# Group

A `.x-group` wraps sibling controls so they read as one joined bar: interior corners flatten, the 1px borders between neighbours collapse to a single hairline, and the hovered or focused control lifts its border and focus ring above its neighbours. No JS — the joining is pure CSS driven by child position.



## Install

```ts
import "elements-kit/ui/styles.css";

// a neutral + an accent scale for theming:
import "elements-kit/ui/styles/palette/slate.css";
import "elements-kit/ui/styles/neutral/slate.css";
import "elements-kit/ui/styles/palette/mint.css";
import "elements-kit/ui/styles/accent/mint.css";
// the group primitive, plus whichever controls you join:
import "elements-kit/ui/group/group.css";
import "elements-kit/ui/button/button.css";
import "elements-kit/ui/text-input/text-input.css";
```

See [Styles](/ui/styles) for the full token system, accent/gray scale lists, and theming knobs.

## API

`.x-group` is a class-only wrapper. Its children are ordinary [`.x-button`](/ui/button), [`.x-text-input`](/ui/text-input), [`.x-select`](/ui/select), and [`.x-toggle`](/ui/toggle) elements — no group-specific markup on them.

```html
<div class="x-group" role="group" aria-label="Text style">
  <button class="unset x-button" data-variant="outline">Bold</button>
  <button class="unset x-button" data-variant="outline">Italic</button>
  <button class="unset x-button" data-variant="outline">Underline</button>
</div>
```

Attach a field to a button by placing them in the same group. Text-inputs and selects flex-grow to fill; buttons and toggles keep their intrinsic width.

```html
<div class="x-group">
  <input class="unset x-text-input" data-variant="surface" placeholder="Search" />
  <button class="unset x-button" data-variant="solid">Go</button>
</div>
```

| Attribute            | Values                                                                              |
| -------------------- | ----------------------------------------------------------------------------------- |
| `role`               | `group` / `toolbar` / `radiogroup` — author-owned, per the controls inside          |
| `data-variant`       | `material` — a floating capsule instead of a joined bar (see [Material](#material)) |
| child `data-size`    | `1`, `2`, `3` — set the **same** value on every child                               |
| child `data-variant` | each control's own variants (`solid`, `outline`, `surface`, …)                      |

The group joins whatever variants you give it. `outline` and `surface` children collapse their two 1px borders into one hairline; adjacent `solid` buttons — which share the accent fill — get a hairline divider in the button's own contrast colour instead.

## Sizing

Every child in a group must carry the same `data-size`. Heights and radii are size-derived, so a mismatched child breaks alignment and leaves the seam uneven. The group forces no uniform radius of its own — each end keeps the control's native outer radius, which respects `data-radius` on a parent (set `data-radius="pill"` for pill ends).

## Material

`data-variant="material"` turns the group into a floating capsule, as in floating toolbars. Children sit apart instead of joining (no overlap, no flattened corners) on a `--color-material` background with its backdrop blur and `--shadow-3`.

It is tuned for `data-size="2"` children: 32px plus 4px padding make a 40px capsule. Text buttons fill the size box, so the padding is even on every side. A [text input](/ui/text-input) or [segmented control](/ui/segmented-control) inside fills the capsule and drops its own background, so the capsule is the field or the track. A focused field rings the whole capsule.

```html
<div class="x-group" data-variant="material" role="group" aria-label="Actions">
  <button class="unset x-button" data-variant="text" data-size="2" data-icon aria-label="Share">…</button>
  <button class="unset x-button" data-variant="text" data-size="2" data-icon aria-label="More">…</button>
</div>

<div class="x-group" data-variant="material">
  <div class="x-text-input" data-variant="soft" data-size="2">
    <span>🔍</span>
    <input class="unset" placeholder="Search" />
  </div>
</div>
```

## How joining works

The group flattens only the interior-facing corners: the first child keeps its leading corners, the last keeps its trailing corners, middle children are squared off. Corners use logical properties, so the join flips correctly under `dir="rtl"`. Neighbours overlap by 1px so their borders never double up, and the interacted control raises its `z-index` so its focus ring paints on top.

> **Two levels of attachment**
>
> Affixes *inside a single field* — a leading icon or a trailing shortcut chip — use the [text-input wrapper](/ui/text-input) form, not a group. Use `.x-group` to join a field to a *separate* control. The two compose: a wrapper-form text-input can itself be a group child, and the group flattens both the wrapper and its inner input on the seam side.

## Accessibility

The group is presentational — it paints, it carries no semantics. You own the roles, exactly as with [segmented control](/ui/segmented-control):

1. **Label the group.** A toolbar of buttons wants `role="toolbar"` (or `role="group"`) plus `aria-label`. A set of exclusive toggles wants `role="radiogroup"` and native radios with a shared `name`.
2. **Keep each control's own semantics.** Buttons stay `<button>`, inputs stay `<input>`. The group changes none of that.
3. **Don't rely on the seam alone** to convey grouping to assistive tech — the role and label do that. In Windows High Contrast the hairlines fall back to a `ButtonText` border so the boundary stays visible.

## When not to use

* **Single-select from a short list** — that's the [segmented control](/ui/segmented-control), which carries a selection indicator and radio semantics. A group has no selected state.
* **Spacing a row of controls** — if you want gaps rather than a joined unit, use flexbox with `gap`. The group is attached-only.
* **Vertical stacks** — v1 is horizontal only.

## Browser support

Works in every browser meeting `elements-kit`'s build targets (Chrome / Firefox 100+, Safari 16+). Corner flattening and the seam overlap use logical border-radius properties and `:is()`, both long-shipped. The solid-button divider uses [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) (Chrome / Edge 111+, Safari 16.2+, Firefox 113+); where it is unsupported, adjacent solid buttons simply show no divider — every other join still renders.

## See also

* [Button](/ui/button) — the most common group child
* [Text Input](/ui/text-input) — bare and wrapper (affix) forms
* [Segmented Control](/ui/segmented-control) — single-select, with an indicator
* [Styles](/ui/styles) — tokens, radius, and theming
