Gesso
Actions

Button group

Joined toolbar buttons. Size comes from the children.

Button group joins buttons into one toolbar control, like a pager or a set of view actions. Neighbors share their inner corners and borders, and the group has no size of its own, so it takes its height from the buttons inside it.

Usage

import { Button } from '@/components/ui/button';
import { ButtonGroup } from '@/components/ui/button-group';

<ButtonGroup role="group" aria-label="View options">
  <Button variant="outline">Filter</Button>
  <Button variant="outline">Display</Button>
</ButtonGroup>

Corners and borders join

Every child after the first loses its start corners and its start border, and every child before the last loses its end corners, so outline buttons share one border line and the row reads as one shape. Give every button the same size, since the group has none to pass down. The rule reaches direct children only, so do not wrap a button in a <div> or <span>. A TooltipTrigger or DropdownMenuTrigger with asChild is fine, because it renders the button itself.

Filled buttons, like default and secondary, have no border and meet edge to edge, so put a ButtonGroupSeparator between them. It is a vertical Separator in bg-border that stretches to the height of the row.

Stacked

orientation="vertical" stacks the buttons and joins them top to bottom instead, for a short column of actions like moving a row up or down. Inside a stacked group, give ButtonGroupSeparator orientation="horizontal".

Text in the row

ButtonGroupText puts a label or a value in the row, like the page number in a pager. It sits on bg-surface-sunken with a border and stretches to the height of the buttons. Pass asChild to render your own element with its styling, such as a <label> for an input in the group. It has no corner radius of its own, so add rounded-control when it starts or ends the row.

import { ButtonGroup, ButtonGroupText } from '@/components/ui/button-group';

<ButtonGroup role="group" aria-label="Pages">
  <Button variant="outline" onClick={previous}>Previous</Button>
  <ButtonGroupText>Page 2 of 8</ButtonGroupText>
  <Button variant="outline" onClick={next}>Next</Button>
</ButtonGroup>
Page 2 of 8

Nested groups keep a gap

A group whose children include other groups sets them 8px apart instead of joining them, so one toolbar can hold several clusters.

Props

ButtonGroup takes every <div> attribute, plus orientation. ButtonGroupText takes every <div> attribute plus asChild, and ButtonGroupSeparator takes every Separator prop.

PropTypeDefaultWhat it does
orientation"horizontal" | "vertical""horizontal"On ButtonGroup. Joins the children side by side, or stacks them and joins them top to bottom.
asChildbooleanfalseOn ButtonGroupText. Renders its one child with the text styling, instead of a <div>.
orientation"horizontal" | "vertical""vertical"On ButtonGroupSeparator. A vertical line between side-by-side buttons. Set "horizontal" in a stacked group.

To style something that is not a ButtonGroup, buttonGroupVariants({ orientation }) returns the classes.

Accessibility

  • ButtonGroup renders a plain <div> with no role, so assistive tech does not hear the buttons as a set. When they belong together, pass role="group" and an aria-label, which it passes through to the div.
  • Each button stays its own tab stop and keeps its own name and role. The group adds no arrow-key movement and no role="toolbar".
  • The focused child rises above its neighbors, so the next button does not cover its focus ring.
  • Focus rings, touch targets, and the disabled and loading states come from each Button.
  • ButtonGroupSeparator is decorative, so assistive tech skips it.
  • ButtonGroupText is plain text to assistive tech. When it names an input, render it as a <label> with asChild and point its htmlFor at the input.

When to use something else

  • Toggle group when the buttons hold a state that stays on, like the current view or bold text. Its outline variant joins the same way, and it adds pressed states, one tab stop, and the arrow keys.

Install

shadcn add @gesso/button-group

On this page