Skip to content

Button group

Joins related buttons into one control, like a record's Edit, Clone and More.

Library
What’s this?

Installation

pnpm dlx shadcn@latest add @wisty/button-group

The CLI installs the Radix or Base UI file to match your project, with what it builds on: separator.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • A few related actions sit together, like a record's Edit, Clone and Delete.
  • A main action has a menu of related ones beside it, as a split button.
  • An input needs a fixed prefix and an action, like a web address with Visit.

Don't use it

  • The buttons stay pressed to show a choice. Use Toggle Group.
  • The actions aren't related, or there are more than about four. Use separate buttons, or Dropdown Menu for the rest.
  • An input needs icons or buttons inside its frame. Use Input Group.

Usage

import { Button } from "@/components/ui/button"
import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from "@/components/ui/button-group"
ButtonGroup
├── ButtonGroupText (optional, fixed text before an Input)
├── Button
├── ButtonGroupSeparator (between filled buttons)
└── DropdownMenuTrigger (optional, a menu at the end)
<ButtonGroup aria-label="Approval">
  <Button variant="outline">Approve</Button>
  <Button variant="outline">Reject</Button>
</ButtonGroup>

Style a menu trigger in the group with className={buttonVariants({ variant: "outline", size: "icon" })}, as the Record actions example does. A ButtonGroup inside another keeps two sets apart, as Nested shows.

Examples

Brand

Two brand buttons, with the inset line the group draws between them.

Split button

A brand button and its options button, split by a `ButtonGroupSeparator`.

Nested

Two `ButtonGroup`s inside a third, kept apart, one with a disabled button.

Text

Fixed text in `ButtonGroupText`, an `Input` that takes the space left, and a button.

https://

Vertical

Two icon buttons stacked with `orientation="vertical"` on `ButtonGroup`.

API reference

PartBuilt on
ButtonGroup<div> (opens in a new tab)
ButtonGroupText<div> (opens in a new tab)
ButtonGroupSeparatorSeparator

ButtonGroup

PropTypeDefault
orientation"horizontal" | "vertical""horizontal"

ButtonGroupText

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.

ButtonGroupSeparator

PropTypeDefaultDescription
orientation"horizontal" | "vertical""vertical"Either vertical or horizontal.

SLDS mapping

ButtonGroup is slds-button-group: the buttons join, inner corners square, each keeping its start border and all but the last dropping their end border. A focused button takes SLDS's group focus, a brand button after a brand button gets SLDS's inset line, and nested groups sit apart as .slds-button-group + .slds-button-group do.

Known gaps:

  • SLDS has no text part. ButtonGroupText is a box joined to the buttons, in the neutral button's border and radius. SLDS's nearest pattern, fixed text beside an input (slds-form-element__addon), sits outside the field without a box.
  • SLDS has no separator element; between brand buttons it draws a line. ButtonGroupSeparator draws that line next to a filled button, and a border-colored one between bordered buttons.
  • SLDS has no vertical group. orientation="vertical" mirrors the horizontal one.
  • In Lightning Blue, SLDS keeps the line between two brand buttons while the second is hovered. Ours drops it on hover.
PropValueSLDSNotes
orientationhorizontalslds-button-groupButtons joined in a row. ButtonGroupText and ButtonGroupSeparator have no SLDS counterpart.
orientationverticalnoneSLDS has no vertical group; it mirrors the horizontal one.

Accessibility

  • Name the group with aria-label, like "Record actions". It has the group role.
  • Name icon-only buttons and triggers with aria-label.
  • ButtonGroupText is plain text, not a label. Name an input beside it with aria-label or a Label.

Responsive behavior

  • The group is as wide as its children and doesn't wrap. Give it a width and an Input inside takes the space left, as the Text example shows.
  • Keep groups to a few short labels, and move extra actions into a menu on narrow screens.

Rules

  • Use variant="outline" for a neutral group, and the brand variant only for the main action.
  • Don't restyle the buttons' borders or corners inside a group. The group sets them.
  • Use ButtonGroupSeparator between filled buttons, as in a split button. Bordered buttons already share a border.