@xsolla/xui-toggle-button-group
v0.223.0
Published
A control for picking one or several options from a linear set of closely related options; useful for filtering or sorting. <!-- BEGIN:xui-mcp-instructions:toggle-button-group --> A group of buttons where one or more options can be selected. Used to switc
Readme
ToggleButtonGroup
A control for picking one or several options from a linear set of closely related options; useful for filtering or sorting.
A group of buttons where one or more options can be selected. Used to switch between modes, apply filters, or pick compact options — both as an immediate inline control and inside a form via Field Group. Not used for navigation.
ToggleButtonGroup can be placed inside a Field Group as the content slot. This gives it a label, helper text, and validation message — the same as any other form control. Use this pattern when the selection is part of a form and needs a label or needs to align visually with adjacent fields.
Checkbox vs ToggleButtonGroup:
- Decision guide
When to use
- When a user needs to select one or more options from a small, fixed set (2–8+ items)
- To switch between views, display modes, or content filters
- When options are equal in weight and benefit from a persistent, visible selection state
- When a compact button-style control fits better than a list of checkboxes
- Inside a form via Field Group — as a styled alternative to radio buttons or checkboxes
When not to use
- When the choice triggers a navigation change — use Tabs
- For a single binary on/off toggle — use a Switch
- When there are more options than fit comfortably in a row
- When options are numerous, have descriptions, or need a long vertical list — use a Context-menu with Checkboxes
Content guidelines
- Keep item labels short — one word or a brief noun phrase (e.g. Day, Week, Month).
- Use parallel structure across all items in the same group (all nouns, or all verbs).
- Icons should reinforce the label meaning; avoid using icons that require the label to be understood.
- If using icon-only items (no label), ensure all icons are recognizable and add a tooltip.
Behaviour guidelines
- Clicking an item toggles it between Active and Default.
- The group supports both single-select (one active at a time) and multi-select (multiple active simultaneously) — the selection model is defined at the product level, not by the component itself.
- A Disable item cannot be clicked and cannot receive Active state.
- The group does not close or collapse on selection — it remains fully visible.
Accessibility
- When used as a single-select control, the group should use role="radiogroup" and each item role="radio" with aria-checked.
- When used as a multi-select control, each item should use role="checkbox" with aria-checked.
- Apply aria-disabled="true" to disabled items.
- Provide an accessible label on the group via aria-label or aria-labelledby.
- Focus should be manageable via arrow keys within the group (roving tabindex pattern).
Installation
npm install @xsolla/xui-toggle-button-groupImports
import { ToggleButtonGroup } from '@xsolla/xui-toggle-button-group';
import type {
ToggleButtonGroupProps,
ToggleButtonGroupItem,
ToggleButtonGroupSize,
ToggleButtonGroupAppearance,
} from '@xsolla/xui-toggle-button-group';Quick start
onChange is typed (value: string | string[]) => void regardless of multiple, so single-select callers cast the value to string.
const [value, setValue] = useState('item1');
<ToggleButtonGroup
items={[
{ id: 'item1', label: 'Option 1' },
{ id: 'item2', label: 'Option 2' },
{ id: 'item3', label: 'Option 3' },
]}
value={value}
onChange={(v) => setValue(v as string)}
/>;API Reference
<ToggleButtonGroup>
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| items | ToggleButtonGroupItem[] | — | Items to display. |
| value | string \| string[] | — | Controlled value; string for single, array for multiple. |
| defaultValue | string \| string[] | — | Initial value for uncontrolled mode. |
| onChange | (value: string \| string[]) => void | — | Fired when the selection changes. |
| size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' | 'sm' | Size variant. |
| appearance | 'separated' \| 'united' | 'separated' | separated adds gaps; united connects items. |
| view | 'horizontal' \| 'vertical' | 'horizontal' | Stack items in a row or column. |
| multiple | boolean | false | Allow multiple selections. |
| fullWidth | boolean | false | Stretch to fill the container. |
| id | string | — | HTML id. |
| testID | string | — | Test identifier for the group root. |
| aria-label | string | — | Accessible label for the group. |
| aria-labelledby | string | — | ID of an element that labels the group. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
ToggleButtonGroupItem
| Field | Type | Description |
| --- | --- | --- |
| id | string | Unique identifier. |
| label | string | Display text. Omit for icon-only items (set aria-label and tooltip). |
| iconLeft | ReactNode | Icon shown before the label. |
| iconRight | ReactNode | Icon shown after the label. |
| disabled | boolean | Disable the individual item: inert, not focusable, painted with the disable palette. |
| locked | boolean | Mark the item as selected and immutable. Keeps the active palette (dimmed), never fires onChange, and stays keyboard-focusable so its tooltip is reachable. Takes precedence over disabled. See Locked items. |
| testID | string | Applied as data-testid on the item's button root — one test id per option, for QA automation. |
| aria-label | string | Accessible label. Required when label is omitted. |
| tooltip | Omit<TooltipProps, 'children'> | Tooltip props for the button (web only). placement defaults to "top" (horizontal) or "right" (vertical); size defaults from theme.sizing.toggleButtonGroup(size).tooltipSize. |
Types
type ToggleButtonGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
type ToggleButtonGroupAppearance = 'separated' | 'united';
type ToggleButtonGroupView = 'horizontal' | 'vertical';
type ToggleButtonGroupItemTooltip = Omit<TooltipProps, 'children'>;Examples
Multiple selection
onChange is still typed (value: string | string[]) => void, so multi-select callers cast the value to string[].
const [values, setValues] = useState(['item1', 'item3']);
<ToggleButtonGroup
multiple
items={[
{ id: 'item1', label: 'Option 1' },
{ id: 'item2', label: 'Option 2' },
{ id: 'item3', label: 'Option 3' },
]}
value={values}
onChange={(v) => setValues(v as string[])}
/>Locked items
An option that is switched on and can never be switched off — an irreversible
opt-in — is a locked item, not a disabled one. Typical cases: an operating
system a launcher already supports, a plan feature that cannot be dropped, a
mandatory permission scope, a region already launched.
const [platforms, setPlatforms] = useState(['windows']);
<ToggleButtonGroup
multiple
items={[
{
id: 'windows',
label: 'Windows',
locked: true,
tooltip: { content: 'Windows is already live and cannot be removed' },
},
{ id: 'macos', label: 'MacOS' },
{ id: 'linux', label: 'Linux' },
]}
value={platforms}
onChange={(v) => setPlatforms(v as string[])}
aria-label="Operating systems"
/>disabled vs locked
| | disabled | locked |
| --- | --- | --- |
| Palette | disable tokens — a selected item loses its "on" signal | active tokens, dimmed — still reads as ON |
| Selected | Only if present in value | Always; the id need not be in value |
| Fires onChange | No | No |
| Native disabled attribute | Yes | No |
| Keyboard focusable | No (tabIndex={-1}) | Yes (tabIndex={0}) |
| aria-disabled | true | true |
| Tooltip reachable by keyboard | No | Yes |
Reach for disabled when the option is unavailable and the user has nothing to
learn from it; reach for locked when the option is on, will stay on, and the
user needs to understand why.
Design tokens. The locked treatment is currently derived from the active palette plus an opacity step. When
bgActiveDisable,textActiveDisableandborderActiveDisableare added totheme.colors.control.toggleButton, the component uses those values instead — no consumer change required.
United appearance, full width
<ToggleButtonGroup
appearance="united"
fullWidth
items={[
{ id: 'day', label: 'Day' },
{ id: 'week', label: 'Week' },
{ id: 'month', label: 'Month' },
]}
defaultValue="week"
/>Per-item test IDs
Set testID on each item to emit a data-testid on that option's button root. QA automation can target options declaratively instead of relying on DOM order.
<ToggleButtonGroup
items={[
{ id: 'day', label: 'Day', testID: 'period-day' },
{ id: 'week', label: 'Week', testID: 'period-week' },
{ id: 'month', label: 'Month', testID: 'period-month' },
]}
defaultValue="week"
aria-label="Period"
/>Icons
import { BadgeDiscount, Crown } from '@xsolla/xui-icons-base';
<ToggleButtonGroup
items={[
{ id: 'sale', label: 'Sale', iconLeft: <BadgeDiscount variant="line" /> },
{ id: 'premium', label: 'Premium', iconRight: <Crown variant="line" /> },
{
id: 'vip',
iconLeft: <Crown variant="line" />,
'aria-label': 'VIP',
tooltip: { content: 'VIP' },
},
]}
defaultValue="sale"
/>Accessibility
role="radiogroup"for single selection,role="group"for multiple selection.- Arrow keys move between items; Enter/Space toggles selection.
aria-checkedandaria-disabledreflect item state.lockeditems follow the focusable-disabled pattern:aria-checked="true"+aria-disabled="true"+tabIndex={0}, with no nativedisabledattribute. They take part in arrow-key navigation and ignore Enter/Space, so a keyboard user can focus the item and read its tooltip without being able to change it.
