Search code examples
reactjsmaterial-ui

How to place any secondary action DOM element next to ListItemButton?


I'm working on a list where users can click on a list item to perform an action but they can also click a button ( with text ) next to the list item to perform another action.

Given this code ( Reproduction Sandbox => https://stackblitz.com/edit/vitejs-vite-m6y3k8n8?file=src%2Froutes%2Findex.tsx )

import { PostAdd } from '@mui/icons-material';
import {
  Button,
  List,
  ListItem,
  ListItemButton,
  ListItemText,
} from '@mui/material';

function Page() {
  return (
    <List>
      <ListItem
        secondaryAction={
            <Button startIcon={<PostAdd />}>Right button</Button>
        }
      >
          <ListItemButton>
            <ListItemText primary={'Text'} />
          </ListItemButton>
      </ListItem>
    </List>
  );
}

It happens that the right button "merges" with the list item button. The right button is inside the list item button and not right next to it.

When I only use a IconButton ( with no text ) instead of the Button everything works as expected. ( See https://stackblitz.com/edit/react-f8cswl8t?file=Demo.tsx ) But I want this button to have some Text so I need the Button element.

My real-world usecase is even more complex since I need to wrap the Button element inside a @tanstack/router Link component like so ( https://stackblitz.com/edit/vitejs-vite-usjbw8tp?file=src%2Froutes%2Findex.tsx )

        secondaryAction={
          <Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
            <Button startIcon={<PostAdd />}>Secondary Action</Button>
          </Link>
        }

How do I solve this for the real-world usecase? Or is it only possible to use IconButton for the secondary action slot?


Solution

  • To resolve your issue (the secondary action button merges with the list item), override Material-UI's default positioning using the sx prop on the ListItem:

    The Fix: We need to tell Material-UI: "Hey, keep the main list item on the left, and the button on the right – don't squish them together!"

    <ListItem
      secondaryAction={
        <Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
          <Button startIcon={<PostAdd />}>Secondary Action</Button>
        </Link>
      }
      sx={{
        '& .MuiListItemSecondaryAction-root': {
          position: 'relative',
          transform: 'none',
          right: 'auto',
          top: 'auto',
        },
      }}
    >
      <ListItemButton>
        <ListItemText primary="Text" />
      </ListItemButton>
    </ListItem>
    

    This should makes the secondary action button appear properly aligned next to the list item content.

    What Changed:

    1. The sx prop adds special styling rules
    2. We told the secondary button area to stop using weird positioning tricks
    3. Now your button stays neatly on the right side, separated from the main list item

    Extra Tip:

    • sx is Material-UI's way of letting you write CSS styles directly in your component
    • The weird class name MuiListItemSecondaryAction-root is part of Material-UI's internal styling – we’re overriding its default behavior

    Note: I HAVEN'T TESTED IT MUCH, SO BE CAREFULL