Experience System
Components

Button Group

Groups related buttons with shared borders and spacing. Use for actions; use ToggleGroup for selection state.

Installation

The components are exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to build a ButtonGroup:

ButtonGroup
├── Button or Input
├── ButtonGroupSeparator (optional)
└── ButtonGroupText (optional)
└── ButtonGroup (optional, nested cluster)

ButtonGroupSeparator and ButtonGroupText are optional. Nest another ButtonGroup when you need spaced clusters (often with className such as gap-2 on the outer group). For mutually exclusive selection, use ToggleGroup instead of this pattern. ButtonGroupText supports asChild via Radix UI Slot.

Usage

import {
  Button,
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from '@by/experience-system';

ButtonGroup, ButtonGroupText, and ButtonGroupSeparator are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.

<ButtonGroup aria-label="Actions">
  <Button variant="outline">One</Button>
  <Button variant="outline">Two</Button>
</ButtonGroup>

Examples

Default

A horizontal group with default Button styling. Provide an aria-label on ButtonGroup when the group does not have a visible text label. The live preview at the top of this page uses the same example (button-group-default).

Orientation

orientation="horizontal" (default) joins controls on the start/end edges; vertical stacks them and adjusts border radii for top/bottom.

Horizontal (default)
Vertical

Size

Button size is set per control; keep sizes consistent within a group for alignment.

Small
Default
Large

Separator

Use ButtonGroupSeparator to visually divide buttons within a group. Buttons with variant outlined often do not need a separator; for other variants a separator improves visual hierarchy. Pick orientation on the separator to match the group: vertical between buttons in a horizontal group, horizontal in a vertical group.

Nested groups

Nest ButtonGroup components to create groups with spacing between them. The outer ButtonGroup can use className (for example gap-2) so nested groups sit side by side with space between clusters.

Split button

Use a combination of regular and icon buttons to emphasize the primary action and hide others in a dropdown menu.

API Reference

Subsection titles name the exports from @by/experience-system. ButtonGroupSeparator wraps the Experience System Separator (Radix Separator under the hood). ButtonGroupText uses Radix Slot when asChild is true.

ButtonGroup

Root container with shared borders and corner radii for direct child controls.

PropTypeDefault
orientationhorizontal | verticalhorizontal
classNamestring

Renders a div with role="group". Also accepts standard div attributes except dir (RTL follows document or parent dir).

Data attributeValues
data-slotbutton-group

ButtonGroupText

Label or static text inside the group (often beside input or Select).

PropTypeDefault
asChildbooleanfalse
classNamestring

Also accepts standard div attributes (or the child element’s props when asChild).

Data attributeValues
data-slotbutton-group-text

ButtonGroupSeparator

Visually divides buttons. Forwards to Separator with decorative set.

PropTypeDefault
orientationhorizontal | verticalvertical
classNamestring

Also accepts props supported by Separator (for example variant, dashed) except orientation is re-declared above. See Radix Separator for primitive behavior.

Data attributeValues
data-slotbutton-group-separator

Accessibility

  • Give the Button Group an accessible name on ButtonGroup with aria-label or aria-labelledby when there is no visible group label.
  • Ensure each control remains keyboard-focusable; the group applies focus z-index so focus-visible rings are visible across adjacent buttons.
  • Icon-only buttons need a discernible name (aria-label or visible text).

Source in the repo: packages/experience-system/src/components/ButtonGroup/ButtonGroup.tsx and variants.ts.