Skip to contents
This page is an adaptation of the related MUI Material UI documentation page.

Button Group

The ButtonGroup component can be used to group related buttons.

Basic button group

The buttons can be grouped by wrapping them with the ButtonGroup component. They need to be immediate children.

muiMaterialPage(
  CssBaseline(),
  ButtonGroup(variant = "contained", `aria-label` = "Basic button group", Button("One"), Button("Two"), Button("Three"))
)
JS code
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';

export default function BasicButtonGroup() {
  return (
    <ButtonGroup variant="contained" aria-label="Basic button group">
      <Button>One</Button>
      <Button>Two</Button>
      <Button>Three</Button>
    </ButtonGroup>
  );
}

Button variants

All the standard button variants are supported.

buttons <- tagList(Button("One"), Button("Two"), Button("Three"))

muiMaterialPage(
  CssBaseline(),
  Box(
    sx = list(display = "flex", flexDirection = "column", alignItems = "center", "& > *" = list(m = 1)),
    ButtonGroup(variant = "outlined", `aria-label` = "Basic button group", buttons),
    ButtonGroup(variant = "text", `aria-label` = "Basic button group", buttons)
  )
)
JS code
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import Box from '@mui/material/Box';

export default function VariantButtonGroup() {
  return (
    <Box
      sx={{
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        '& > *': {
          m: 1,
        },
      }}
    >
      <ButtonGroup variant="outlined" aria-label="Basic button group">
        <Button>One</Button>
        <Button>Two</Button>
        <Button>Three</Button>
      </ButtonGroup>
      <ButtonGroup variant="text" aria-label="Basic button group">
        <Button>One</Button>
        <Button>Two</Button>
        <Button>Three</Button>
      </ButtonGroup>
    </Box>
  );
}

Sizes and colors

The size and color props can be used to control the appearance of the button group.

muiMaterialPage(
  CssBaseline(),
  Box(
    sx = list(display = "flex", flexDirection = "column", alignItems = "center", "& > *" = list(m = 1)),
    ButtonGroup(size = "small", `aria-label` = "Small button group", buttons),
    ButtonGroup(color = "secondary", `aria-label` = "Medium-sized button group", buttons),
    ButtonGroup(size = "large", `aria-label` = "Large button group", buttons)
  )
)
JS code
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import ButtonGroup from '@mui/material/ButtonGroup';

const buttons = [
  <Button key="one">One</Button>,
  <Button key="two">Two</Button>,
  <Button key="three">Three</Button>,
];

export default function GroupSizesColors() {
  return (
    <Box
      sx={{
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        '& > *': {
          m: 1,
        },
      }}
    >
      <ButtonGroup size="small" aria-label="Small button group">
        {buttons}
      </ButtonGroup>
      <ButtonGroup color="secondary" aria-label="Medium-sized button group">
        {buttons}
      </ButtonGroup>
      <ButtonGroup size="large" aria-label="Large button group">
        {buttons}
      </ButtonGroup>
    </Box>
  );
}

Vertical group

The button group can be displayed vertically using the orientation prop.

muiMaterialPage(
  CssBaseline(),
  Box(
    sx = list(display = "flex", "& > *" = list(m = 1)),
    ButtonGroup(orientation = "vertical", `aria-label` = "Vertical button group", buttons),
    ButtonGroup(orientation = "vertical", `aria-label` = "Vertical button group", variant = "contained", buttons),
    ButtonGroup(orientation = "vertical", `aria-label` = "Vertical button group", variant = "text", buttons)
  )
)
JS code
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import Box from '@mui/material/Box';

const buttons = [
  <Button key="one">One</Button>,
  <Button key="two">Two</Button>,
  <Button key="three">Three</Button>,
];

export default function GroupOrientation() {
  return (
    <Box
      sx={{
        display: 'flex',
        '& > *': {
          m: 1,
        },
      }}
    >
      <ButtonGroup orientation="vertical" aria-label="Vertical button group">
        {buttons}
      </ButtonGroup>
      <ButtonGroup
        orientation="vertical"
        aria-label="Vertical button group"
        variant="contained"
      >
        {buttons}
      </ButtonGroup>
      <ButtonGroup
        orientation="vertical"
        aria-label="Vertical button group"
        variant="text"
      >
        {buttons}
      </ButtonGroup>
    </Box>
  );
}

Split button

ButtonGroup can also be used to create a split button. The dropdown can change the button action (as in this example) or be used to immediately trigger a related action.

The MUI demo positions a Popper() with a React ref. In R, Menu.triggerId() opens the options next to the arrow button (see Menu), and the selected option is kept in the URL with reactRouter (#/merge/<index>):

library(reactRouter)

options <- c("Create a merge commit", "Squash and merge", "Rebase and merge")

splitButton <- function(selectedIndex) {
  tagList(
    ButtonGroup(
      variant = "contained",
      `aria-label` = "Button group with a nested menu",
      Button(onClick = JS(sprintf("() => { console.info('You clicked %s'); }", options[selectedIndex])), options[selectedIndex]),
      Button(
        id = "split-button-toggle",
        size = "small",
        `aria-label` = "select merge strategy",
        `aria-haspopup` = "menu",
        Icon("arrow_drop_down")
      )
    ),
    Menu.triggerId(
      triggerId = "split-button-toggle",
      id = "split-button-menu",
      anchorOrigin = list(vertical = "bottom", horizontal = "right"),
      transformOrigin = list(vertical = "top", horizontal = "right"),
      lapply(seq_along(options), function(index) {
        MenuItem(
          component = "a",
          href = paste0("#/merge/", index),
          disabled = index == 3,
          selected = index == selectedIndex,
          options[index]
        )
      })
    )
  )
}

muiMaterialPage(
  useMaterialIconsFilled = TRUE,
  CssBaseline(),
  RouterProvider(
    router = createHashRouter(
      lapply(1:2, function(index) Route(path = paste0("merge/", index), element = splitButton(index))),
      Route(path = "*", element = splitButton(2))
    )
  )
)
JS code
import * as React from 'react';
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';

const options = ['Create a merge commit', 'Squash and merge', 'Rebase and merge'];

export default function SplitButton() {
  const [open, setOpen] = React.useState(false);
  const anchorRef = React.useRef(null);
  const [selectedIndex, setSelectedIndex] = React.useState(1);

  const handleClick = () => {
    console.info(`You clicked ${options[selectedIndex]}`);
  };

  const handleMenuItemClick = (event, index) => {
    setSelectedIndex(index);
    setOpen(false);
  };

  const handleToggle = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  const handleClose = (event) => {
    if (anchorRef.current && anchorRef.current.contains(event.target)) {
      return;
    }

    setOpen(false);
  };

  return (
    <React.Fragment>
      <ButtonGroup
        variant="contained"
        ref={anchorRef}
        aria-label="Button group with a nested menu"
      >
        <Button onClick={handleClick}>{options[selectedIndex]}</Button>
        <Button
          size="small"
          aria-controls={open ? 'split-button-menu' : undefined}
          aria-expanded={open ? 'true' : undefined}
          aria-label="select merge strategy"
          aria-haspopup="menu"
          onClick={handleToggle}
        >
          <ArrowDropDownIcon />
        </Button>
      </ButtonGroup>
      <Popper
        sx={{ zIndex: 1 }}
        open={open}
        anchorEl={anchorRef.current}
        role={undefined}
        transition
        disablePortal
      >
        {({ TransitionProps, placement }) => (
          <Grow
            {...TransitionProps}
            style={{
              transformOrigin:
                placement === 'bottom' ? 'center top' : 'center bottom',
            }}
          >
            <Paper>
              <ClickAwayListener onClickAway={handleClose}>
                <MenuList id="split-button-menu" autoFocusItem>
                  {options.map((option, index) => (
                    <MenuItem
                      key={option}
                      disabled={index === 2}
                      selected={index === selectedIndex}
                      onClick={(event) => handleMenuItemClick(event, index)}
                    >
                      {option}
                    </MenuItem>
                  ))}
                </MenuList>
              </ClickAwayListener>
            </Paper>
          </Grow>
        )}
      </Popper>
    </React.Fragment>
  );
}

Disabled elevation

You can remove the elevation with the disableElevation prop.

muiMaterialPage(
  CssBaseline(),
  ButtonGroup(disableElevation = TRUE, variant = "contained", `aria-label` = "Disabled button group", Button("One"), Button("Two"))
)
JS code
import ButtonGroup from '@mui/material/ButtonGroup';
import Button from '@mui/material/Button';

export default function DisableElevation() {
  return (
    <ButtonGroup
      disableElevation
      variant="contained"
      aria-label="Disabled button group"
    >
      <Button>One</Button>
      <Button>Two</Button>
    </ButtonGroup>
  );
}

Loading

Use the loading prop from Button to set buttons in a loading state and disable interactions.

muiMaterialPage(
  useMaterialIconsFilled = TRUE,
  CssBaseline(),
  ButtonGroup(
    variant = "outlined",
    `aria-label` = "Loading button group",
    Button("Submit"),
    Button("Fetch data"),
    Button(loading = TRUE, loadingPosition = "start", startIcon = Icon("save"), "Save")
  )
)
JS code
import ButtonGroup from '@mui/material/ButtonGroup';
import Button from '@mui/material/Button';
import SaveIcon from '@mui/icons-material/Save';

export default function LoadingButtonGroup() {
  return (
    <ButtonGroup variant="outlined" aria-label="Loading button group">
      <Button>Submit</Button>
      <Button>Fetch data</Button>
      <Button loading loadingPosition="start" startIcon={<SaveIcon />}>
        Save
      </Button>
    </ButtonGroup>
  );
}

Button groups in Shiny apps

Each button of a group can be a Button.shinyInput(), which reports its own click counter:

library(shiny)
library(muiMaterial)

ui <- muiMaterialPage(
  CssBaseline(),
  Box(
    sx = list(p = 2),
    ButtonGroup(
      variant = "contained",
      Button.shinyInput("one", "One"),
      Button.shinyInput("two", "Two"),
      Button.shinyInput("three", "Three")
    ),
    verbatimTextOutput("clicked")
  )
)

server <- function(input, output, session) {
  last <- reactiveVal("none")
  observeEvent(input$one, last("One"))
  observeEvent(input$two, last("Two"))
  observeEvent(input$three, last("Three"))
  output$clicked <- renderText(paste("Last clicked:", last()))
}

shinyApp(ui, server)

For a group of mutually exclusive options, use Toggle Button Group.