|Kit

ButtonGroup

Polymorphic

Join a row or column of Buttons and ActionIcons into one segmented control — inner corners flattened, adjacent borders collapsed to a single hairline.

ButtonGroup wraps a set of Button or ActionIcon siblings so they sit edge-to-edge as one unit: the inner corners are flattened and the adjacent borders collapse into a single hairline. Use it for view switchers, segmented toolbars, and split actions.

It is a pure layout wrapper — it deliberately doesn't touch its children's props. Give every child the same size (and usually the same variant / color) so their edges line up.

import { Button } from '@42/ui-react/button';
import { ButtonGroup } from '@42/ui-react/button-group';

<ButtonGroup>
  <Button variant="outline" color="gray">Left</Button>
  <Button variant="outline" color="gray">Center</Button>
  <Button variant="outline" color="gray">Right</Button>
</ButtonGroup>

Playground

Flip orientation and switch presets — the code panel updates as you go.

Component

SpinnerLoading preview

Props

Code

<Component orientation="horizontal" />

Basic

Only the outermost corners keep their radius; the inner ones square off. Adjacent borders overlap into one hairline instead of doubling up.

Code

<ButtonGroup>  <Button variant="outline" color="gray">Left</Button>  <Button variant="outline" color="gray">Center</Button>  <Button variant="outline" color="gray">Right</Button></ButtonGroup>

Orientation

orientation="vertical" joins the children into a column instead of a row — the top/bottom edges flatten rather than the left/right ones. The horizontal join uses logical properties, so it mirrors correctly under dir="rtl".

Code

<ButtonGroup orientation="vertical">  <Button variant="outline" color="gray">Top</Button>  <Button variant="outline" color="gray">Middle</Button>  <Button variant="outline" color="gray">Bottom</Button></ButtonGroup>

Action icons

Group ActionIcons into a compact toolbar. Each still needs its own aria-label.

Code

<ButtonGroup>  <ActionIcon variant="outline" aria-label="Bold"><Bold /></ActionIcon>  <ActionIcon variant="outline" aria-label="Italic"><Italic /></ActionIcon>  <ActionIcon variant="outline" aria-label="Underline"><Underline /></ActionIcon></ButtonGroup>

Split action

Mix a Button with a trailing ActionIcon — the classic split-button pattern (a primary action plus a menu trigger).

Code

<ButtonGroup>  <Button variant="filled" color="brand">Save</Button>  <ActionIcon variant="filled" color="brand" aria-label="More save options">    <ChevronDown />  </ActionIcon></ButtonGroup>

Variants

Every Button / ActionIcon variant works, and the group keeps adjacent children visually separated whichever one you use:

  • Bordered variants (outline, light, default) collapse their two shared borders into a single hairline.
  • Borderless variants (filled, subtle) have no border to collapse, so the group adds an adaptive divider on the shared edge — a translucent currentColor line that reads as a soft light seam on a filled fill and a faint neutral one on subtle. Because it follows the text color it stays correct on every palette color and in dark mode.

A hovered or focused child always lifts above its neighbours, so its full border and focus ring stay visible.

Mixed variants

Children don't have to share a variant — but keep in mind this is not recommended. The seam adapts across a variant boundary: bordered neighbours collapse to a hairline, borderless ones show the adaptive divider. Keep the size consistent so the heights still line up.

Code

<ButtonGroup>  <Button variant="subtle">Day</Button>  <Button variant="filled" color="brand">Week</Button>  <Button variant="subtle">Month</Button></ButtonGroup><ButtonGroup>  <Button variant="outline" color="gray">Cancel</Button>  <Button variant="filled" color="brand">Confirm</Button></ButtonGroup>

Equal heights

If a horizontal row ends up with different-height children — most often a mix of sizes — the group stretches them all to the tallest so the row stays aligned. Each button keeps its own width, and ActionIcon keeps its square. For a uniform look, still prefer a single size; this is the safety net when you can't.

Code

<ButtonGroup>  <Button size="sm">Small</Button>  <Button size="lg">Large</Button>  <Button size="sm">Small</Button></ButtonGroup>

Polymorphic with asChild

ButtonGroup renders a div with role="group". Use asChild to swap that host element — e.g. a <section aria-label="…"> — while keeping the joining styles.

<ButtonGroup asChild>
  <section aria-label="Text formatting">
    <ActionIcon variant="outline" aria-label="Bold"><Bold /></ActionIcon>
    <ActionIcon variant="outline" aria-label="Italic"><Italic /></ActionIcon>
  </section>
</ButtonGroup>

API

Prop

Type

All other props are forwarded to the underlying div (or the asChild element).

Accessibility

  • The root carries role="group". When the grouping is meaningful to assistive tech (e.g. a toolbar or a set of related actions), label it with aria-label / aria-labelledby.
  • The group doesn't manage focus or selection — each child stays an independent, individually focusable control. For a single-choice segmented toggle with roving focus, use SegmentGroup instead.
  • Icon-only children must set their own aria-label (see ActionIcon).

On this page