Joins adjacent Buttons into a single control, collapsing the inner corners and overlapping borders.

Last updated

Show code
<ButtonGroup label="View">
<Button variant="outline" color="neutral">Day</Button>
<Button variant="outline" color="neutral">Week</Button>
<Button variant="outline" color="neutral">Month</Button>
</ButtonGroup>

Add

npx @ava-ui/apogee add button-group

No Apogee dependencies of its own — it’s a layout wrapper. Designed to compose with Button (add that separately: npx @ava-ui/apogee add button).

Usage

---
import ButtonGroup from '@/components/apogee/ButtonGroup/ButtonGroup.astro'
import Button from '@/components/apogee/Button/Button.astro'
---
<ButtonGroup label="Alignment">
  <Button variant="outline" color="neutral">Left</Button>
  <Button variant="outline" color="neutral">Center</Button>
  <Button variant="outline" color="neutral">Right</Button>
</ButtonGroup>

API

@composable — exposed to Content Editors in AVA. Any other native <div> attribute passes through.

Prop Type Default Description
label string — Accessible label for the group (sets aria-label on the role="group" root).
disabled boolean false Disables every button inside. The root is a <fieldset>, so this is native and needs no JS.
class string — Extra classes, merged last.

ButtonGroup applies no visual style to itself beyond the join — it targets its direct children ([&>*:not(:first-child)] / [&>*:not(:last-child)]) to overlap borders and drop the inner corner radius. It reads no color tokens of its own; those come from whatever’s composed inside (see Button’s Tokens).

States

Hover, focus and active belong to the composed Buttons. A focused item lifts above its overlapping neighbour so its whole outline shows. disabled on the group disables every button at once.

Show code
<ButtonGroup label="Alignment">
<Button variant="outline" color="neutral">Left</Button>
<Button variant="outline" color="neutral">Center</Button>
<Button variant="outline" color="neutral">Right</Button>
</ButtonGroup>
<ButtonGroup label="Alignment" disabled>
<Button variant="outline" color="neutral">Left</Button>
<Button variant="outline" color="neutral">Center</Button>
<Button variant="outline" color="neutral">Right</Button>
</ButtonGroup>

Examples

Mixed content — a group works with any adjacent controls, not only Button:

Show code
<ButtonGroup label="Zoom">
<Button variant="outline" color="neutral" iconOnly aria-label="Zoom out">−</Button>
<Button variant="outline" color="neutral" iconOnly aria-label="Zoom in">+</Button>
</ButtonGroup>

Accessibility

  • Renders role="group" with aria-label (from the label prop) so screen readers announce it as a set, not stray buttons.
  • Always pass label — without it the group has no accessible name.
  • Keyboard — unchanged from the composed children: Tab moves through each button in order (no roving tabindex; this isn’t a radiogroup or tablist).

Tokens

None of its own. Borders, backgrounds, and focus rings come from the composed components (typically Button — see its Tokens table).

Do / Don’t

  • Do use it for a small set of related, mutually exclusive or peer actions (view toggles, pagination arrows).
  • Do always pass label.
  • Don’t mix more than one variant inside a group — it reads as one control, so its buttons should look uniform.
  • Don’t use it for unrelated actions that happen to sit next to each other — use plain spacing instead.

Content

  • Labels follow Button’s content rules (short, verb-led where the action is a command; nouns for a view/filter toggle).