Skip to main content

ButtonGroup

MD3 Expressive button group: an invisible container that spaces Button / IconButton children and reshapes them on press. It can also own a single- or multi-select model — a connected single-select group is the Expressive replacement for the segmented button.

Standard​

standard (the default) spaces the buttons by the size's between-space (18 / 12 / 8 / 8 / 8dp for xs–xl). Pressing a button widens it by 15% and squeezes its neighbours — press and hold to see it.

<ButtonGroup aria-label="Actions">
<IconButton variant="tonal" icon={<EditIcon />} aria-label="Edit" />
<Button variant="filled">Share</Button>
<IconButton variant="tonal" icon={<SearchIcon />} aria-label="Search" />
</ButtonGroup>

Connected​

connected joins the buttons with a 2dp gap: the outer corners stay fully round, the inner corners use a per-size radius that tightens while pressed.

Sizes​

Match size on the group to the size of its buttons — it sets the between-space (standard) or the inner corners and 48dp minimum width (connected).

<ButtonGroup variant="connected" size="md">
<Button size="md">One</Button>
…
</ButtonGroup>

Single select​

Set selectionMode="single" and give each child a value; the group owns the state through value / defaultValue / onChange(event, value). selectionRequired keeps one item selected (re-activating it does nothing).

const [view, setView] = useState<string | null>('week')

<ButtonGroup
variant="connected"
selectionMode="single"
selectionRequired
value={view}
onChange={(event, next) => setView(next)}
aria-label="Calendar view"
>
<Button variant="tonal" value="day">Day</Button>
<Button variant="tonal" value="week">Week</Button>
<Button variant="tonal" value="month">Month</Button>
</ButtonGroup>

Multi select​

selectionMode="multiple" turns the children into toggle buttons; the value is a string[].

<ButtonGroup
variant="connected"
selectionMode="multiple"
defaultValue={['bold', 'underline']}
onChange={(event, values) => setStyles(values)}
aria-label="Text style"
>
<IconButton value="bold" icon={<BoldIcon />} aria-label="Bold" />
…
</ButtonGroup>

Vertical​

Accessibility​

  • Arrow keys move focus between the buttons: ← / → when horizontal (mirrored in RTL), ↑ / ↓ when vertical. Home / End jump to the ends; disabled buttons are skipped.
  • selectionMode="single" renders a radiogroup of radio items with a single Tab stop — arrow keys move focus and selection.
  • selectionMode="multiple" renders toggle buttons with aria-pressed.
  • Name the group with aria-label / aria-labelledby, and give icon-only children their own aria-label.

Props​

PropTypeDefaultDescription
childrenReactNode—`Button` / `IconButton` children (give them a `value` when the group has a `selectionMode`).
defaultValuestring | number | readonly string[] | string[] | nullnull []Unused without a selection model (kept for HTMLAttributes compatibility). Uncontrolled initial selected item value. Uncontrolled initial selected item values.
onChangeChangeEventHandler<HTMLDivElement, Element> | ((event: SyntheticEvent<Element, Event>, value: string | null) => void) | ((event: SyntheticEvent<...>, value: string[]) => void)—Native `change` events bubbling from descendants. Fires with the triggering event and the next selected value. Fires with the triggering event and the next selected values.
orientationenum'horizontal'Layout direction.
selectionModeenum'none'
selectionRequiredbooleanfalse falseKeep one item selected: re-activating the selected item does nothing. Keep at least one item selected.
sizeenum'sm'Match the child button size: sets the between-space (standard) or the inner corners and 48dp minimum width (connected).
valuestring | string[] | null—Controlled selected item value (`null` = none). Controlled selected item values.
variantenum'standard'`standard` spaces buttons and widens the pressed one; `connected` joins them into a single shape with small inner corners.