Button Group
Accessible, unstyled adjacent action group with optional toolbar roving focus.
| Package | @vizejs/ui/button-group |
| Maturity | stable |
| Own the source | vize lib pull button-group |
| Requires | context |
| Aliases | button group, button bar, action group, toolbar |
| Covers | WAI-ARIA Toolbar, React Aria Toolbar, shadcn/ui Button |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { ButtonGroup, ButtonGroupItem } from "@vizejs/ui/button-group";
Or copy the source into your project with vize lib pull button-group (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Text-formatting toolbar with arrow-key roving focus and one disabled action. -->
<script setup lang="ts">
import { ref } from "vue";
import { ButtonGroup, ButtonGroupItem } from "@vizejs/ui/button-group";
const lastAction = ref("none");
</script>
<template>
<div>
<ButtonGroup
role="toolbar"
aria-label="Text formatting"
@press="(value) => (lastAction = value)"
>
<ButtonGroupItem value="bold">Bold</ButtonGroupItem>
<ButtonGroupItem value="italic">Italic</ButtonGroupItem>
<ButtonGroupItem value="underline">Underline</ButtonGroupItem>
<ButtonGroupItem value="strikethrough" disabled>Strikethrough</ButtonGroupItem>
</ButtonGroup>
<output>Last action: {{ lastAction }}</output>
</div>
</template>
API
ButtonGroup
Source: src/families/actions/button-group/button-group.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
PrimitiveAs |
"div" |
Native element, custom element, or component to render. |
role |
ButtonGroupRole |
"group" |
Accessible grouping semantics for adjacent actions. |
disabled |
boolean |
false |
Disable every item and remove the group from roving focus. |
orientation |
ButtonGroupOrientation |
"horizontal" |
Directional layout hint used by toolbar arrow-key roving focus. |
loop |
boolean |
true |
Whether arrow-key navigation wraps at the first and last enabled item. |
rovingFocus |
boolean |
undefined |
Whether items participate in a single-tabstop roving focus model. Defaults to true for role="toolbar" and false for plain groups. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the group. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the group. |
Events
| Event | Payload | Description |
|---|---|---|
press |
[value: string, nativeEvent: MouseEvent] |
Fired after an enabled item is activated by pointer or keyboard. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ButtonGroupSlotState |
Compound ButtonGroup items. Receives grouping, availability, and navigation state. |
Exposed
| Member | Type | Description |
|---|---|---|
disabled |
boolean |
Whether the group suppresses every item activation. |
orientation |
ButtonGroupOrientation |
Directional layout hint used by toolbar arrow-key roving focus. |
role |
ButtonGroupRole |
Accessible grouping role rendered by the root. |
rovingFocus |
boolean |
Whether items participate in a single-tabstop roving focus model. |
state |
ButtonGroupState |
Stable state token for styling and tests. |
activeValue |
string | null |
Currently active item value, or null before an item is focusable. |
element |
PrimitiveElement | null |
Rendered root element or component instance. |
focus |
(options?: FocusOptions) => void |
Move focus to the active or first enabled item. |
focusValue |
(value: string, options?: FocusOptions) => boolean |
Move focus to an enabled item by value and report whether it was found. |
ButtonGroupItem
Source: src/families/actions/button-group/button-group-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
PrimitiveAs |
"button" |
Native element, custom element, or component to render. |
native |
boolean |
auto |
Whether the rendered target already implements native button semantics. |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
value (required) |
string |
required |
Stable item value emitted by item and group press events. |
disabled |
boolean |
false |
Disable this item while preserving the rest of the group. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label this item. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe this item. |
Events
| Event | Payload | Description |
|---|---|---|
press |
[value: string, nativeEvent: MouseEvent] |
Fired after user activation reaches this enabled item. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
ButtonGroupItemSlotState |
Renders item contents with current availability and navigation state. |
Exposed
| Member | Type | Description |
|---|---|---|
value |
string |
Item value used by the group navigation and press contract. |
disabled |
boolean |
Whether this item or its group suppresses activation. |
orientation |
ButtonGroupOrientation |
Directional layout hint inherited from the group. |
state |
ButtonGroupItemState |
Stable state token for styling and tests. |
element |
PrimitiveElement | null |
Rendered item element or component instance. |
focus |
(options?: FocusOptions) => void |
Move focus to the rendered item. |
Behavior
| ID | Component | Scenario | Behavior | Assertion |
|---|---|---|---|---|
| B1 | button-group.vue |
grouped actions | renders an accessible group with item data, part, and disabled hooks |
renders grouped button semantics without adding visual CSS |
| B2 | button-group.vue |
toolbar navigation | role="toolbar" opts into one-tabstop roving focus by default |
toolbar roving focus follows orientation and skips disabled |
| B3 | button-group.vue |
group navigation override | plain groups may opt into roving focus while preserving their ARIA role | plain groups can opt into roving focus without toolbar role |
| B4 | button-group-item.vue |
activation | enabled items emit item and group press events in dispatch order |
pointer and keyboard activation emit value-carrying events |
| B5 | button-group-item.vue |
disabled | disabled groups and items suppress activation and leave tab order safely | disabled groups and items suppress activation |
| B6 | button-group-item.vue |
custom element semantics | non-native items receive role="button" and synthesize keyboard clicks |
custom items expose button semantics and keyboard activation |
| B7 | both | public instance | root and items expose focus methods and immutable state snapshots | exposes focus, focusValue, activeValue, and item state |
| B8 | button-group-item.vue |
duplicate value | mount and reactive value changes reject duplicate item values | rejects duplicate item values... |
| B9 | button-group-item.vue |
provider boundary | items fail loudly when rendered outside a matching group provider | items require a matching group provider |
Contract
ButtonGroup is a headless compound primitive for adjacent actions such as
button bars, editor toolbars, card action rows, and destructive-confirmation
clusters. It owns no selection model and emits no visual CSS. Consumers style
through data-vize-ui, data-state, data-disabled, data-orientation,
data-role, and part attributes.
role="group" preserves the native tab order by default. role="toolbar"
defaults to a single tab stop with arrow-key movement because that is the WAI-ARIA
toolbar expectation. Consumers may explicitly set rovingFocus on either role.
Items require a unique string value so item-level and group-level press
events, activeValue, roving tabindex, and focusValue() stay stable across
polymorphic rendering, slot changes, and DOM reordering. Duplicate values throw
VIZE_UI_BUTTON_GROUP_VALUE_DUPLICATE during registration or reactive value
changes before more than one item can own the active tab stop.