ButtonGroup
A named row or column of related Buttons, joined into one block with shared edges or spaced apart. The group sets one size and variant for its Buttons.
Preview
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>Usage
When to use
- Keep two to five related actions together, such as Copy, Edit and Delete in a toolbar.
- A main action with a menu of more next to it (a split button).
When not to use
- A choice that stays selected, such as a view or alignment. Use ToggleGroup or Tabs.
- Unrelated actions, or a main action next to Cancel. Place the Buttons side by side.
- Moving between pages of results. Use Pagination.
Variants
orientation
A row or a column of Buttons.
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>size
The Button size for every Button in the group, the same steps as Button: sm 28px, md 32px, lg 36px (the group default), xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. A Button that sets its own size keeps it.
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup size="sm" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup size="md" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup size="xl" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>variant
The Button variant for every Button in the group. A Button that sets its own variant keeps it. An attached group draws one shadow for the variant (shadow-low for secondary and solid, shadow-med for the glossy ones) instead of one per Button. Put a ButtonGroupSeparator between attached solid Buttons.
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
<ButtonGroup aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="solid" aria-label="File actions">
<Button>Duplicate</Button>
<ButtonGroupSeparator />
<Button>Edit</Button>
<ButtonGroupSeparator />
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="soft" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="ghost" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="destructive" aria-label="File actions">
<Button>Duplicate</Button>
<ButtonGroupSeparator />
<Button>Edit</Button>
<ButtonGroupSeparator />
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="destructive-soft" aria-label="File actions">
<Button>Duplicate</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="glossy" aria-label="File actions">
<Button>Duplicate</Button>
<ButtonGroupSeparator />
<Button>Edit</Button>
<ButtonGroupSeparator />
<Button>Delete</Button>
</ButtonGroup>
<ButtonGroup variant="destructive-glossy" aria-label="File actions">
<Button>Duplicate</Button>
<ButtonGroupSeparator />
<Button>Edit</Button>
<ButtonGroupSeparator />
<Button>Delete</Button>
</ButtonGroup>Examples
Attached actions
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="File actions">
<Button>Rename</Button>
<Button>Duplicate</Button>
<Button>Archive</Button>
</ButtonGroup>Icons and labels
Icon-only Buttons carry their own name.
import { IconBold, IconItalic, IconUnderline } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="Text formatting">
<Button iconOnly aria-label="Bold">
<IconBold size={16} />
</Button>
<Button iconOnly aria-label="Italic">
<IconItalic size={16} />
</Button>
<Button iconOnly aria-label="Underline">
<IconUnderline size={16} />
</Button>
</ButtonGroup>Split button
A main action and a menu with more. Solid Buttons have no border, so a separator marks where one ends.
import { IconChevronDown, IconSparkles } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
<ButtonGroup aria-label="Publish" variant="solid">
<Button leadingIcon={<IconSparkles size={16} />}>Publish</Button>
<ButtonGroupSeparator />
<Button iconOnly aria-label="More publish options">
<IconChevronDown size={16} />
</Button>
</ButtonGroup>Spaced apart
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="Pages" attached={false}>
<Button leadingIcon={<IconArrowLeft size={16} />}>Previous</Button>
<Button trailingIcon={<IconArrowRight size={16} />}>Next</Button>
</ButtonGroup>Vertical
import { IconMinus, IconPlus } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="Zoom" orientation="vertical">
<Button iconOnly aria-label="Zoom in">
<IconPlus size={16} />
</Button>
<Button iconOnly aria-label="Zoom out">
<IconMinus size={16} />
</Button>
</ButtonGroup>Full width
import { IconDownload, IconLink, IconMail } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
<ButtonGroup aria-label="Share" fullWidth>
<Button leadingIcon={<IconLink size={16} />}>Share link</Button>
<Button leadingIcon={<IconMail size={16} />}>Email</Button>
<Button leadingIcon={<IconDownload size={16} />}>Download</Button>
</ButtonGroup>Do and don't
Do
- Group two to five closely related actions, such as Rename, Duplicate and Archive, or a main action and a menu with more.
- Name the group with aria-label or a visible heading through aria-labelledby.
- Use one variant for the whole group. Use a ButtonGroupSeparator between attached solid Buttons.
Don't
- Do not group unrelated actions or a main action with a Cancel. Place those Buttons side by side instead.
- Do not use a group for a choice that stays selected; use ToggleGroup or Tabs.
- Do not put a link Button in a group.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| orientation | enum | horizontal | vertical | horizontal | A row or a column of Buttons. |
| attached | boolean | - | true | Joins the Buttons into one block with shared edges. false spaces them out with a spacing-sm gap. |
| size | enum | sm | md | lg | xl | lg | The Button size for every Button in the group, the same steps as Button: sm 28px, md 32px, lg 36px (the group default), xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. A Button that sets its own size keeps it. |
| variant | enum | secondary | solid | soft | ghost | destructive | destructive-soft | glossy | destructive-glossy | secondary | The Button variant for every Button in the group. A Button that sets its own variant keeps it. An attached group draws one shadow for the variant (shadow-low for secondary and solid, shadow-med for the glossy ones) instead of one per Button. Put a ButtonGroupSeparator between attached solid Buttons. |
| fullWidth | boolean | - | false | Fills the width of its container. In a row the Buttons share the width equally. |
Slots
| Name | Required | Description |
|---|---|---|
| children | yes | Buttons, and ButtonGroupSeparator between attached solid Buttons. Only direct Button children take the group size and variant. |
Anatomy
| Part | Description |
|---|---|
| root | The named group. It draws no fill: the Buttons keep their own fill, border and motion. Attached, the Buttons drop their own shadows and the group draws one shadow (the variant's step) around the whole block. |
| item | A Button inside the group. Attached, only the outer corners stay round and neighbouring borders collapse into one line. |
| separator | An optional 1px line between two solid Buttons, which have no border to show where one ends. |
Accessibility
Role: group
| Keys | Action |
|---|---|
| Tab, Shift+Tab | Moves through the Buttons one by one. Every Button keeps its own tab stop. |
| Space, Enter | Presses the focused Button. |
- The group requires aria-label or aria-labelledby, so screen readers announce what the Buttons belong to.
- Every Button keeps its own name. An icon-only Button requires aria-label.
- The focused Button rises above its neighbours, so its focus ring is never cut by the next Button.
- ButtonGroupSeparator is decorative and hidden from screen readers.
- For a choice that stays selected, use ToggleGroup or Tabs: the Buttons of a group carry no pressed state.
Install
npx werewolf-ui add button-groupRun npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.