Let a button group own its selection

Plan step 22, actions.md § Missing (Selection-required / multi-select
semantics): M3 lists single-select, multi-select and selection-required as
button-group configurations, and `<x-button-group connected>` left
selection entirely to the caller's own `aria-pressed`.

`selection="single|multi"`, with `required`, takes `aria-pressed` over:
pressing a button writes its `value` to `wire:model` or `x-model`,
deselects the others in `single`, and refuses the press that would leave
nothing selected. With no model it reads the buttons' own `aria-pressed`
once and goes on from there.

`<x-group>` and this do not absorb one another, and the header and
SKILL.md say why: `<x-group>` is for a choice whose options are data — it
renders real inputs, posts in a plain form and paints its own segments —
and stays the first thing to reach for; a selection group governs buttons
the caller writes, manages state and shape, and leaves each button to draw
its own colours from its own `:selected`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:43:34 +02:00
co-authored by Claude Opus 5
parent 5d0e9bc12f
commit 6af501c966
4 changed files with 166 additions and 2 deletions
@@ -282,6 +282,17 @@ Clusters: `<x-menu-separator />` draws M3's line, `<x-menu-group gap>` M3 Expres
A row of `<x-button>`s: `<x-button-group label="View" size="md">…</x-button-group>`. `connected` sets them 2px apart with small inner corners (a selected toggle rounds fully). Pass the `size` of the buttons inside. `shape="square"` is M3's square group and covers every button in it, so do not write `shape` on each one: a connected group's ends square to the corner its inner edges take, a standard group's buttons take the square corner scale, and a selected button still rounds — M3 has the toggle morph the other way.
`selection` is M3's third configuration — `single`, `multi`, and either with `required` ("selection-required"). The group then owns `aria-pressed`:
```blade
<x-button-group connected selection="single" required wire:model.live="view" label="View">
<x-button label="Day" value="day" variant="tonal" :selected="$view === 'day'" />
<x-button label="Week" value="week" variant="tonal" :selected="$view === 'week'" />
</x-button-group>
```
Pressing a button writes its `value` (an array with `multi`) to `wire:model` or `x-model`, deselects the others in `single`, and with `required` refuses the press that would leave nothing selected; without a model it reads the buttons' own `aria-pressed` once and goes on from there. A button with no `value` is known by its label. The group manages state and shape, not colour — each button draws its selected colours from its own `:selected`, so bind both from one property as above. **Reach for `<x-group>` first**: it is the component for a choice whose options are data (real radios or checkboxes, a plain form post, the browser's keyboard, segments that paint themselves). `<x-button-group selection>` is for buttons you write yourself — icons, tooltips, mixed content — and never becomes a form control.
### `<x-group>`
A choice between a few options as a connected button group of native radios (checkboxes with `multiple`):