Passer au contenu principal

ButtonGroup

Implemented

Group of related buttons with role=group semantics. Declarative items mode (array of ButtonGroupItem with optional icon + hidden) or slot mode (caller-provided children). Aligns start/end, wraps responsively.

Categorymolecules
Sourcepatterns/molecules/buttongroup.html
AI HintUse for grouped UI patterns. Combine atoms into functional units.

Group of related buttons with role="group" semantics. Declarative items mode (array of ButtonGroupItem with optional icon + hidden) or slot mode (caller-provided children). Aligns start/end, wraps responsively.

ButtonGroup

Content & Layout molecule (Family 6)

Renders a role="group" row of Button atoms — declarative items mode or slot mode (caller-provided children). Items and children are mutually exclusive: when both are given, items win.

Items mode — declarative actions

Click a button — each item carries its own onClick handler.

Hidden item — permission-gated (DOM omission)

Delete is hidden: not rendered at all — never reachable by Tab or a screen reader.

Slot mode — caller-provided children

align="end" — pushed right inside a flex parent

Form footer

This component supports light and dark mode.

Usage

ButtonGroup is the DS molecule for a row of related actions: it wraps the buttons in role="group" semantics with a required accessible name, composes the Button atom (variants, sizes, loading, disabled, 44px tap floor), and wraps responsively on narrow viewports. Use it instead of a hand-rolled flex gap-2 div of Buttons — the group semantics and label are the point.

import { ButtonGroup, Button } from '@epasslive/design-system'

// Declarative items mode
<ButtonGroup
  ariaLabel="Quick actions"
  items={[
    { id: 'pdf', label: 'Download PDF', variant: 'primary', onClick: downloadPdf },
    { id: 'wallet', label: 'Add to Apple Wallet', variant: 'secondary' },
    { id: 'share', label: 'Share link', variant: 'ghost' },
  ]}
/>

// Slot mode — caller-provided children
<ButtonGroup ariaLabel="Edit actions">
  <Button variant="secondary" size="sm">Cancel</Button>
  <Button size="sm">Save</Button>
</ButtonGroup>

// Permission-gated item — hidden is omitted from the DOM entirely
<ButtonGroup
  ariaLabel="Admin actions"
  items={[
    { id: 'edit', label: 'Edit', variant: 'primary' },
    { id: 'delete', label: 'Delete', variant: 'danger', hidden: !canDelete },
    { id: 'export', label: 'Export CSV', variant: 'secondary' },
  ]}
/>

Two modes, one contract

  • items mode — declarative: one source-of-truth array, Button atoms rendered in order, per-item variant/size/loading/disabled/icon/hidden. Best for action clusters driven by data or permissions.
  • slot mode (children) — wrap your own Buttons (or mixed controls, e.g. <Select> + Buttons) in the group semantics. Best for form footers where buttons carry non-declarative props (type="submit", refs).
  • mutual exclusivity — when both items and children are given, items wins.

Alignment

align="end" applies ml-auto to the group container — it only works when the parent is a flex container (the auto margin then pushes the group to the end of the main axis). Inside a block-level parent the ml-auto is inert and the group stays left-aligned. All current align="end" consumers sit in flex/toolbar parents.

Props

Prop
Type
Default
Description
ariaLabel*
string
—

Accessible name for the group (WAI-ARIA 1.2). Required — the container renders role="group" and must be named.

items
ButtonGroupItem[]
—

Declarative mode: renders Button atoms in order. Each item: id, label, icon?, variant?, size?, loading?, disabled?, hidden?, onClick?, ariaLabel?. See the ButtonGroupItem table below.

children
React.ReactNode
—

Slot mode: wraps caller-provided Buttons (or mixed controls) in the group semantics. Use when buttons need non-declarative props (form submission, refs). Items wins when both are given.

align
'start' | 'end'
'start'

Horizontal alignment. end = ml-auto on the container — only effective inside a flex parent (inert in a block container).

className
string
—

Extra classes merged onto the group container (flex flex-wrap items-center gap-3). Note: consumer gap-* utilities lose to the hardcoded gap-3 in the stylesheet order.

id
string
—

Stable id for the container. Defaults to a React.useId()-generated id (SSR-safe).

ButtonGroupItem

Fields of each entry in the items array (items mode):

Prop
Type
Default
Description
id*
string
—

Stable React key.

label*
string
—

Visible button text (the accessible name unless ariaLabel overrides it).

icon
React.ReactNode
—

Leading icon — wrapped in an aria-hidden span (decorative, excluded from the a11y tree).

variant
'primary' | 'secondary' | 'ghost' | 'danger'
Button default

Per-item Button atom variant.

size
'sm' | 'md' | 'lg'
Button default

Per-item Button atom size. All sizes keep the 44px tap floor.

loading
boolean
false

Loading state — sets aria-busy and disables interaction (Button atom).

disabled
boolean
false

Disabled state (native disabled attribute).

hidden
boolean
false

Visually omit this item (permission-gated actions, feature flags) while keeping the declarative list stable. Omitted from the DOM entirely, not clipped.

onClick
() => void
—

Click handler for the rendered Button.

ariaLabel
string
—

Per-button accessible name override — takes precedence over label.

Accessibility

  • Container renders role="group" with an aria-label — the ariaLabel prop is required by the type system (WAI-ARIA 1.2: a group must be named).
  • Natural Tab order — every button is individually tabbable. This is the WAI-ARIA group pattern, not a toolbar: arrow-key roving belongs to toolbar/segmented patterns (see the dedicated TableToolbar / TextFormatToolbar molecules).
  • hidden items are omitted from the DOM entirely — not visually clipped and not aria-hidden — so neither Tab nor screen readers ever encounter them (WCAG 2.1.1).
  • Per-item ariaLabel overrides the accessible name when the visible label needs more context; leading icons are aria-hidden decoration.
  • Focus-visible ring (2px teal) and the 44px tap floor are inherited from the Button atom (WCAG 2.4.7 / 2.5.5).

Props

Preview

ButtonGroup

Prop
Type
Required
Default
Description
ariaLabel
string
Required
—

Accessible name for the group (WAI-ARIA 1.2).

items
ButtonGroupItem[]
Optional
—

Declarative mode: renders Button atoms in order. Each item: id, label, icon?, variant?, size?, loading?, disabled?, hidden?, onClick?, ariaLabel?.

children
React.ReactNode
Optional
—

Slot mode: wraps caller-provided Buttons. Items wins when both are given.

align
'start' | 'end'
Optional
'start'

Horizontal alignment (end = ml-auto for right-aligned action rows).

className
string
Optional
—

Additional CSS classes on the group container.

id
string
Optional
—

Root element id.

Quick use:<ButtonGroup />

Accessibility

ARIA rolegroup

ARIA

1 rule

WAI-ARIA APG

Container must have role='group' with aria-label

Keyboard

1 rule

WCAG 2.1.1

Buttons remain in tab order; hidden items are omitted from the DOM, not visually clipped

Usage

Import

tsxSnippet
import { ButtonGroup } from "@epasslive/design-system"

Example 1

tsxSnippet
<ButtonGroup ariaLabel="Quick actions" items={items} />

Example 2

tsxSnippet
<ButtonGroup ariaLabel="Edit actions"><Button>Save</Button></ButtonGroup>

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

Design System v4.1.0 | ePassLive