Button Group

Segments sharing one rounded chrome with inner dividers -- row actions or a split button.

Usage

import {
  ButtonGroup,
  ButtonGroupButton,
  ButtonGroupSeparator,
  ButtonGroupText,
} from '@epilot/spark-ui/button-group'
<ButtonGroup aria-label="Row actions">
  <ButtonGroupButton>Edit</ButtonGroupButton>
  <ButtonGroupSeparator />
  <ButtonGroupButton>Delete</ButtonGroupButton>
</ButtonGroup>

ButtonGroup is the shared chrome: it owns the rounded box, the fill, and the foreground color, and its segments (ButtonGroupButton, ButtonGroupText) are chrome-less -- they inherit the group's look and only add their own hover and focus states. ButtonGroupSeparator is an author-placed divider: put one between segments that need a visual break, and skip it between segments that read as one action.

This is a layout group, not a toolbar: segments are independent tab stops in DOM order, not a roving-tabindex widget. For a single-select group of segments, use Segmented Control instead.

Examples

Row actions

A plain group of segments sharing one chrome.

Split button with a menu

Pass a ButtonGroupButton as render to Dropdown Menu's trigger to build a split button: a primary action segment plus a chevron segment that opens a menu of related actions.

API Reference

ButtonGroup

PropTypeDefaultDescription
styleVariant'solid' | 'soft' | 'surface' | 'ghost' | 'outline''soft'Always emitted as data-style; segments read the inherited --spark-style-* tokens.
color'accent' | 'neutral' | 'success' | 'warning' | 'danger''neutral'accent omits data-accent-color; neutralgray, successgreen, warningorange, dangerred.
size'small' | 'default''default'Sets the group's height, font size, and icon size (h-9/h-10, matching Button's scale). Inherits to segments.

The ends follow the theme radius knob (same expression as Button).

The group also forwards all other div attributes, including className (merged with the base styling), and a ref to the root <div>. Pass aria-label when segments don't self-describe -- the root carries role="group".

ButtonGroupButton

An interactive segment. Renders Base UI Button, so it accepts all of Button's underlying props, including render for polymorphism (e.g. a split-button dropdown trigger) and disabled.

ButtonGroupText

An inert segment for status/label text -- no hover, no cursor, no keyboard interaction. Use it as the first segment of a status pill.

ButtonGroupSeparator

A thin aria-hidden divider (bg-current, so it recolors with the group's color). Place it between segments yourself; it is not inserted automatically.

Accessibility

  • The root carries role="group" -- document your own aria-label when segments don't self-describe.
  • Segments are independent tab stops in DOM order. There is no roving tabindex or arrow-key navigation; that belongs to Segmented Control.
  • ButtonGroupButton maps disabled to native disabled/aria-disabled semantics via Base UI's Button, the same as the standalone Button.
  • Give an icon-only segment its own aria-label.
  • ButtonGroupText never carries role="status" -- add that yourself on the consumer side if the text should be announced on change.
  • The focus ring is inset (focus-visible:inset-ring-2) because the group clips its segments with overflow-hidden.

On this page