# ButtonGroup

**When to use:** Joins a row (or column) of buttons into a single seamless control, merging their borders and rounding only the outer corners. Use for a segmented toggle ("Day / Week / Month"), an icon toolbar (bold/italic/underline), or any set of related actions that should read as one control rather than separate buttons. Purely a layout wrapper, no interaction logic of its own, so any button-like children work, not just `Button`.

**Keywords:** segmented control

**Import:** `import { ButtonGroup, ButtonGroupOrientation } from '@ahrowe/ui'`

**Orientation:** `ButtonGroupOrientation.Horizontal` (default) | `ButtonGroupOrientation.Vertical`

```tsx
import { ButtonGroup, ButtonGroupOrientation, Button, ButtonStyleType } from '@ahrowe/ui';

// Segmented toggle
<ButtonGroup>
  <Button styleType={ButtonStyleType.Outlined}>Day</Button>
  <Button styleType={ButtonStyleType.Outlined}>Week</Button>
  <Button styleType={ButtonStyleType.Outlined}>Month</Button>
</ButtonGroup>

// Icon toolbar
<ButtonGroup>
  <Button styleType={ButtonStyleType.Outlined} icon={faBold} aria-label="Bold" />
  <Button styleType={ButtonStyleType.Outlined} icon={faItalic} aria-label="Italic" />
  <Button styleType={ButtonStyleType.Outlined} icon={faUnderline} aria-label="Underline" />
</ButtonGroup>

// Vertical
<ButtonGroup orientation={ButtonGroupOrientation.Vertical}>
  <Button styleType={ButtonStyleType.Outlined}>Bold</Button>
  <Button styleType={ButtonStyleType.Outlined}>Italic</Button>
  <Button styleType={ButtonStyleType.Outlined}>Underline</Button>
</ButtonGroup>

// With a separator — filled styles (Primary/Delete) have no border of their own to merge,
// so a vertical Divider gives adjacent buttons a visible seam instead of blending together
import { Divider, DividerOrientation } from '@ahrowe/ui';

<ButtonGroup>
  <Button styleType={ButtonStyleType.Filled}>Save</Button>
  <Divider orientation={DividerOrientation.Vertical} />
  <Button styleType={ButtonStyleType.Filled}>Save & new</Button>
</ButtonGroup>

// Nested groups — a toolbar made of two self-contained sub-groups, spaced apart rather
// than merged into one continuous strip
<ButtonGroup>
  <ButtonGroup>
    <Button styleType={ButtonStyleType.Outlined} icon={faBold} aria-label="Bold" />
    <Button styleType={ButtonStyleType.Outlined} icon={faItalic} aria-label="Italic" />
    <Button styleType={ButtonStyleType.Outlined} icon={faUnderline} aria-label="Underline" />
  </ButtonGroup>
  <ButtonGroup>
    <Button styleType={ButtonStyleType.Outlined} icon={faAlignLeft} aria-label="Align left" />
    <Button styleType={ButtonStyleType.Outlined} icon={faAlignCenter} aria-label="Align center" />
    <Button styleType={ButtonStyleType.Outlined} icon={faAlignRight} aria-label="Align right" />
  </ButtonGroup>
</ButtonGroup>

// With an Input — a search bar. Input's visible border lives on its own container/fieldset
// elements, buried inside a <label>, not its root — but it still flattens correctly with no
// extra props, via the CSS custom-property mechanism described below
import { Input } from '@ahrowe/ui';

<ButtonGroup>
  <Input placeholder="Search…" value={search} onChange={setSearch} />
  <Button styleType={ButtonStyleType.Filled} icon={faMagnifyingGlass} aria-label="Search" />
</ButtonGroup>
```

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `children` | `ReactNode` | The buttons (or button-like elements) to join, in order |
| `orientation` | `ButtonGroupOrientation` | Layout direction (default `Horizontal`) |

**How it works:** the first and last children keep rounded outer corners; every child in between is flattened on both sides. Adjacent borders are collapsed with a `-1px` margin so two 1px borders don't stack into a visible double line, and whichever child is hovered/focused gets a `z-index` bump so its border/focus ring isn't clipped by its neighbors.

**One border per seam:** the overlap alone keeps both borders, and at a fractional scale (browser zoom, a 125% or 150% display) they round to different device pixels, so one seam reads 2px next to a 1px one. Each child therefore clips away the 1px it shares with the next one, and the later child's border is the one you see, as it always was. A focused child keeps its whole border, and the next child clips its shared 1px instead. This is a `clip-path` on the children, so a `clip-path` of your own on a grouped child is overridden.

Flattening reaches a child's visible border even when that border doesn't live on the child's own root element — the case for `Input`, `Dropdown`, and everything built on either of them (`NumberInput`, `DatePicker`, `TimeInput`, `InputDropdown`, `Textarea`), whose actual border sits on a `fieldset` nested inside a `<label>`, as well as `ColorPicker`'s swatch and `Checkbox`'s checkmark box. Alongside the direct `border-radius` reset it applies to each child's root, `ButtonGroup` also sets `--group-radius-tl`/`-tr`/`-br`/`-bl` custom properties per position. Unlike `border-radius` itself, custom properties inherit through any nesting depth, so a descendant whose own `border-radius` reads `var(--group-radius-tl, var(--default-border-radius))` (etc.) picks up the right corner overrides no matter how deep it's nested — no consumer-facing prop needed.

**Separators:** any child with `role="separator"` (what `Divider` sets) is excluded from that border-collapsing margin, on both sides — it stays fully visible between its neighbors instead of being partially hidden under one of them. Use a vertical `Divider` (see [Divider.md](Divider.md)) between two buttons when their style has no visible border of its own to merge (`Primary`, `Delete`), so there's still a clear seam between them.

**Note:** the corner-flattening rules key off DOM position (`:first-child`/`:last-child`), so they work for direct `Button`/`Input`/`Dropdown`-family children out of the box. A child that renders as a wrapper around its own button (e.g. a component that portals its real content elsewhere) won't get the flattening automatically. `SplitButton` is exactly that case (its second child is a `Popover`-wrapped button), which is why it sets its own corner overrides directly rather than relying on `ButtonGroup`'s automatic ones. See [SplitButton.md](SplitButton.md). A fully custom component whose CSS doesn't reference the `--group-radius-*` variables can still be flattened manually the same way, via its own `classNames`/`styles` slots if it exposes the right one.

**Slots:** none — `ButtonGroup` has no named inner elements, only `className`/`style` on the root.
