Button group

Buttons can be grouped by wrapping them in buttonGroup(). They need to be immediate children: the group rounds the first and last button and pulls the rest together, so anything between them breaks the seam.

Basic button group

buttonGroup({ label: 'Basic button group' },
  button('One'),
  button('Two'),
  button('Three'),
)

Variants

The group itself carries no colour. Set variant and color on the buttons, and keep them the same across the group — that is what makes it read as one control.

stack({ gap: 'md', align: 'flex-start' },
  buttonGroup({ label: 'Solid' },
    button({ variant: 'solid' }, 'One'),
    button({ variant: 'solid' }, 'Two'),
    button({ variant: 'solid' }, 'Three'),
  ),
  buttonGroup({ label: 'Outline' },
    button({ variant: 'outline', color: 'neutral' }, 'One'),
    button({ variant: 'outline', color: 'neutral' }, 'Two'),
    button({ variant: 'outline', color: 'neutral' }, 'Three'),
  ),
  buttonGroup({ label: 'Soft' },
    button({ variant: 'soft' }, 'One'),
    button({ variant: 'soft' }, 'Two'),
    button({ variant: 'soft' }, 'Three'),
  ),
)

Sizes and colors

stack({ gap: 'md', align: 'flex-start' },
  buttonGroup({ label: 'Small' },
    button({ size: 'sm', variant: 'outline', color: 'neutral' }, 'Left'),
    button({ size: 'sm', variant: 'outline', color: 'neutral' }, 'Center'),
    button({ size: 'sm', variant: 'outline', color: 'neutral' }, 'Right'),
  ),
  buttonGroup({ label: 'Large' },
    button({ size: 'lg', variant: 'soft', color: 'danger' }, 'Cancel'),
    button({ size: 'lg', variant: 'soft', color: 'danger' }, 'Discard'),
  ),
)

Buttons with an href group exactly the same way — for a row of things that each go somewhere, none of which is the one you are on.

buttonGroup({ label: 'Share' },
  button({ href: '#rss', variant: 'outline', color: 'neutral' }, 'RSS'),
  button({ href: '#json', variant: 'outline', color: 'neutral' }, 'JSON'),
  button({ href: '#sitemap', variant: 'outline', color: 'neutral' }, 'Sitemap'),
)

Button group or toggle group?

A button group is a container: it joins whatever you put in it and holds no state. If one of the items is selected — a segmented control, a filter, the section you are currently in — that is toggle group, which builds the items from data and marks the active one for you.

The rule of thumb: if pressing one makes the others wrong, it is a toggle group. If each does its own separate thing, it is a button group.

buttonGroup — three separate actions
toggleGroup — one choice out of three
stack({ gap: 'lg' },
  stack({ gap: 'xs' },
    text({ variant: 'caption', tone: 'muted' }, 'buttonGroup — three separate actions'),
    buttonGroup({ label: 'Row actions' },
      button({ variant: 'outline', color: 'neutral' }, 'Edit'),
      button({ variant: 'outline', color: 'neutral' }, 'Duplicate'),
      button({ variant: 'outline', color: 'neutral' }, 'Delete'),
    ),
  ),
  stack({ gap: 'xs' },
    text({ variant: 'caption', tone: 'muted' }, 'toggleGroup — one choice out of three'),
    toggleGroup({
      label: 'Text alignment',
      value: 'Center',
      items: ['Left', 'Center', 'Right'],
    }),
  ),
)

With an icon button

buttonGroup({ label: 'Editor actions' },
  button({ variant: 'outline', color: 'neutral' }, 'Save'),
  iconButton({
    label: 'More actions',
    variant: 'outline',
    color: 'neutral',
    icon: icon('more-horizontal'),
  }),
)

Props

PropTypeDefaultDescription
labelstringAccessible name for the group; becomes aria-label on role="group".

Everything else falls through to the wrapper. The buttons inside take their own props — see button().