# ButtonGroup (/docs/button-group)





`ButtonGroup` wraps a set of [`Button`](/docs/button) or [`ActionIcon`](/docs/action-icon) 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.

```tsx
import { Button } from "@42-central/ui-react/button";
import { ButtonGroup } from "@42-central/ui-react/button-group";

<ButtonGroup>
  <Button variant="outline" color="neutral">
    Left
  </Button>
  <Button variant="outline" color="neutral">
    Center
  </Button>
  <Button variant="outline" color="neutral">
    Right
  </Button>
</ButtonGroup>;
```

## Playground [#playground]

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

<story.Playground />

## Basic [#basic]

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

<Preview align="center" code="&#x22;<ButtonGroup>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Left</Button>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Center</Button>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Right</Button>\n</ButtonGroup>&#x22;">
  <ButtonGroup>
    <Button variant="outline" color="neutral">
      Left
    </Button>

    <Button variant="outline" color="neutral">
      Center
    </Button>

    <Button variant="outline" color="neutral">
      Right
    </Button>
  </ButtonGroup>
</Preview>

## Orientation [#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"`.

<Preview align="center" code="&#x22;<ButtonGroup orientation=\&#x22;vertical\&#x22;>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Top</Button>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Middle</Button>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Bottom</Button>\n</ButtonGroup>&#x22;">
  <ButtonGroup orientation="vertical">
    <Button variant="outline" color="neutral">
      Top
    </Button>

    <Button variant="outline" color="neutral">
      Middle
    </Button>

    <Button variant="outline" color="neutral">
      Bottom
    </Button>
  </ButtonGroup>
</Preview>

## Action icons [#action-icons]

Group [`ActionIcon`](/docs/action-icon)s into a compact toolbar. Each still needs its own `aria-label`.

<Preview align="center" code="&#x22;<ButtonGroup>\n  <ActionIcon variant=\&#x22;outline\&#x22; aria-label=\&#x22;Bold\&#x22;><Bold /></ActionIcon>\n  <ActionIcon variant=\&#x22;outline\&#x22; aria-label=\&#x22;Italic\&#x22;><Italic /></ActionIcon>\n  <ActionIcon variant=\&#x22;outline\&#x22; aria-label=\&#x22;Underline\&#x22;><Underline /></ActionIcon>\n</ButtonGroup>&#x22;">
  <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>
</Preview>

## Split action [#split-action]

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

<Preview align="center" code="&#x22;<ButtonGroup>\n  <Button variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22;>Save</Button>\n  <ActionIcon variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22; aria-label=\&#x22;More save options\&#x22;>\n    <ChevronDown />\n  </ActionIcon>\n</ButtonGroup>&#x22;">
  <ButtonGroup>
    <Button variant="filled" color="brand">
      Save
    </Button>

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

## Variants [#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.

<Preview align="block" code="null">
  <div className="flex flex-col gap-3">
    <ButtonGroup>
      <Button variant="filled" color="brand">
        One
      </Button>

      <Button variant="filled" color="brand">
        Two
      </Button>

      <Button variant="filled" color="brand">
        Three
      </Button>
    </ButtonGroup>

    <ButtonGroup>
      <Button variant="subtle">
        One
      </Button>

      <Button variant="subtle">
        Two
      </Button>

      <Button variant="subtle">
        Three
      </Button>
    </ButtonGroup>

    <ButtonGroup>
      <Button variant="light" color="blue">
        One
      </Button>

      <Button variant="light" color="blue">
        Two
      </Button>

      <Button variant="light" color="blue">
        Three
      </Button>
    </ButtonGroup>

    <ButtonGroup>
      <Button>
        One
      </Button>

      <Button>
        Two
      </Button>

      <Button>
        Three
      </Button>
    </ButtonGroup>
  </div>
</Preview>

## Mixed variants [#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.

<Preview align="block" code="&#x22;<ButtonGroup>\n  <Button variant=\&#x22;subtle\&#x22;>Day</Button>\n  <Button variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22;>Week</Button>\n  <Button variant=\&#x22;subtle\&#x22;>Month</Button>\n</ButtonGroup>\n\n<ButtonGroup>\n  <Button variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Cancel</Button>\n  <Button variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22;>Confirm</Button>\n</ButtonGroup>&#x22;">
  <div className="flex flex-col gap-3">
    <ButtonGroup>
      <Button variant="subtle" size="xl">
        Day
      </Button>

      <Button variant="filled" color="brand" size="xs">
        Week
      </Button>

      <Button variant="subtle">
        Month
      </Button>
    </ButtonGroup>

    <ButtonGroup>
      <Button variant="outline" color="neutral">
        Cancel
      </Button>

      <Button variant="filled" color="brand">
        Confirm
      </Button>
    </ButtonGroup>
  </div>
</Preview>

## Equal heights [#equal-heights]

If a horizontal row ends up with different-height children — most often a mix of `size`s — 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.

<Preview align="center" code="&#x22;<ButtonGroup>\n  <Button size=\&#x22;sm\&#x22;>Small</Button>\n  <Button size=\&#x22;lg\&#x22;>Large</Button>\n  <Button size=\&#x22;sm\&#x22;>Small</Button>\n</ButtonGroup>&#x22;">
  <ButtonGroup>
    <Button size="sm">
      Small
    </Button>

    <Button size="lg">
      Large
    </Button>

    <Button size="sm">
      Small
    </Button>
  </ButtonGroup>
</Preview>

## Polymorphic with `asChild` [#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.

```tsx
<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 [#api]

<TypeTable
  type="{
  orientation: {
    type: &#x22;'horizontal' | 'vertical'&#x22;,
    default: &#x22;'horizontal'&#x22;,
    description:
      &#x22;Join the children into a row (flattens the inline edges, RTL-aware) or a column (flattens the block edges).&#x22;,
  },
  asChild: {
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
    description:
      &#x22;Render children as the root element instead of a div (Ark UI polymorphism).&#x22;,
  },
  className: {
    type: &#x22;string&#x22;,
    description:
      &#x22;Merged onto the group root; the layout classes still apply.&#x22;,
  },
  testId: {
    type: &#x22;string&#x22;,
    description: &#x22;data-testid on the group root. See the Testing guide.&#x22;,
  },
}"
/>

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

## Accessibility [#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 the same joined look with a selection and roving focus, use [`ButtonSelect`](/docs/button-select) (one value) or [`ButtonMultiSelect`](/docs/button-multi-select) (several). For a sliding-pill segmented toggle, use [`SegmentGroup`](/docs/segment-group).
* Icon-only children must set their own `aria-label` (see [`ActionIcon`](/docs/action-icon)).
