FluereToggleGroup turns several FluereToggleButtons into a semantically grouped set: role="group", a single Tab stop inside the group (roving tabindex), arrow-key / Home / End focus movement, and single-select (string) or multi-select (string[]) values.
WinUI has no ToggleGroup control: the
microsoft-ui-xamltagwinui3/release/2.5.1only shipsToggleButton(git ls-tree -r winui3/release/2.5.1 | grep -i togglegroupcomes back empty). The split here is therefore: each item's visuals follow WinUIToggleButtonexactly (every item reuses the very sameFluereToggleButtonSFC, so there is no second state table), while the grouping semantics follow the WAI-ARIA toggle button group contract plus reka-ui — that layer has no A-level source to align with.
v-model holds the current value (string) and each FluereToggleGroupItem provides its own value. A single-select group keeps one item pressed; clicking the pressed item again clears the selection (the value becomes undefined — ARIA toggle buttons may all be unpressed, unlike a RadioGroup which must keep one selected).
Current alignment: Align left
A single-select group keeps one item pressed; clicking the pressed item again clears the selection (ARIA toggle button semantics). Tab into the group, then use the arrow keys or Home / End to move focus. Hovering and pressing each deepen the fill one step (NeutralBackground1Hover / Pressed when off, BrandBackgroundHover / Pressed when on).
If your product requires "one item must stay selected", fold undefined back to the previous value:
<FluereToggleGroup :model-value="align" @update:model-value="(value) => (align = value ?? align)">
<!-- … -->
</FluereToggleGroup>
With type="multiple" the v-model is a string[] and clicking adds or removes an element. When type is omitted it is inferred from the shape of modelValue / defaultValue (an array means multi-select).
Applied: Bold
disabled on the group is layered onto every item (the whole group is inert and the arrow keys never enter it). To disable individual items, pass disabled on those FluereToggleGroupItems — the arrow keys skip them.
Entire group disabled
Individual item disabled
size on the group sets one uniform size for its items (the 24 / 32 / 40 steps of FluereToggleButton); a size on the item wins. orientation="vertical" stacks the group and swaps the arrow keys from left/right to up/down. Without modelValue the group is uncontrolled (the underlying primitive keeps the state; default-value seeds it); with it, the group is controlled.
Small (24px, uncontrolled via default-value)
Medium (32px, controlled via v-model)
Large (40px, vertical)
role="group"; when the group has no visible heading, pass label to provide its aria-label.<button type="button"> and expresses its pressed state through aria-pressed ("mixed" for a three-state item); the accessible name comes from the visible text, or from label for icon-only items.← / → (or ↑ / ↓ for a vertical group) and Home / End move focus, disabled items are skipped, and loop (default true) wraps around. The focus ring reuses the 0.4.3 two-layer focus-rectangle primitive (useFocusVisual).<button>.| Item | Value |
|---|---|
| Control | WinUI 3 has no ToggleGroup control; visuals follow ToggleButton, grouping semantics follow WAI-ARIA / reka-ui |
| tag/commit | winui3/release/2.5.1 / ba3a8d5 (local snapshot temp/winui-src) |
| Visual source | src/controls/dev/CommonStyles/ToggleButton_themeresources.xaml (full mapping table in the toggle-button.vue file header) |
| Behavior source | src/dxaml/xcp/dxaml/lib/ToggleButton_Partial.cpp (ChangeVisualState / OnToggleImpl) |
| Grouping engine | reka-ui 2.10.1: ToggleGroupRoot (value semantics + roving focus), RovingFocusItem (roving tabindex) |
FluereToggleGroupItem renders FluereToggleButton itself (toggle-group-item.vue has no second <style> block and declares no colour or stroke at all), so buttons inside and outside a group share one state table, one geometry and one focus primitive — they cannot drift apart.ToggleGroupRoot produces role="group", manages the single / multiple value semantics and provides roving focus; the item uses RovingFocusItem (not reka's ToggleGroupItem, which renders reka's own neutral button look) plus injection of the reka group context to read and write the value.undefined; use the one-liner from "Basic usage" when one item must stay selected.modelValue === undefined it is forwarded as-is so the primitive self-manages, and the update:modelValue it emits is written back locally; when controlled, only the prop is honoured.size down to items through its own provide/inject (a library extension documented in toggle-group/context.ts) so a group cannot mix 24 / 32 / 40 px items.SystemColor*); ② the container gap is our own choice (--spacingHorizontalXS, 4px) because WinUI has no control to align with; ③ reka's name / required are not exposed — add a hidden input yourself when the group must be submitted with a form.FluereToggleGroup| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | string[] | — | Current value (v-model); omit it for an uncontrolled group |
defaultValue | string | string[] | — | Initial value for the uncontrolled case |
type | 'single' | 'multiple' | inferred from the value | Single (string) / multiple (string[]); an array means 'multiple' |
disabled | boolean | false | Disables the whole group |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout direction; also selects left/right vs up/down arrow navigation |
loop | boolean | true | Arrow keys wrap around between the first and last item |
dir | 'ltr' | 'rtl' | — | Reading direction (rarely needed) |
size | 'small' | 'medium' | 'large' | — | Uniform size for the items; an item's own size wins |
label | string | — | Accessible name of the group (equivalent to aria-label) |
| Emit | Payload | Description |
|---|---|---|
update:modelValue | string | string[] | undefined | Value change; a single-select group emits undefined when cleared |
| Slot | Description |
|---|---|
default | The list of FluereToggleGroupItems |
FluereToggleGroupItem| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The item's value inside the group (required) |
disabled | boolean | false | Disables this item (layered on top of the group disabled) |
size | 'small' | 'medium' | 'large' | inherited | Size step; inherits the group size, then falls back to 'medium' |
block | boolean | false | Stretches the item to the container width |
label | string | — | Accessible name (equivalent to aria-label); required for icon-only |
icon | Component | — | Icon component (@fluere-vue/icons), decorative and hidden from AT |
iconOnly | boolean | false | Renders the icon only (compact square) |
| Slot | Description |
|---|---|
default | The button label |
icon | Replaces the built-in icon |
FluereToggleGroupItemmust live insideFluereToggleGroup: the group context comes from reka's provide/inject, and using the item outside a group throws a readable error. Its visuals, disabled colours and focus ring all come fromFluereToggleButton, so those three tables apply to every item in the group as well.