XDSToggleButton@xds/core · ToggleButton
Usage
ToggleButton switches between selected and unselected states to represent a persistent on/off choice. Use it standalone for binary actions like bold, mute, or favorite, or inside a ToggleButtonGroup for single-select or multi-select toolbar controls.Best practices
| Guidance | Practices |
|---|---|
| Do | Use a filled or colored icon for the pressed state so users can see the current state at a glance — an outline star vs a solid star, for example. |
| Do | Keep the label identical between pressed and unpressed states. Let the visual treatment (icon, weight, background) communicate the change. |
| Do | Wrap related toggles in a ToggleButtonGroup with an accessible label so screen readers announce them as a connected set. |
| Don't | Don't use a ToggleButton for one-time actions like "Submit" or "Delete" — those are regular Buttons, not toggles. |
| Don't | Don't mix ToggleButtons with regular Buttons inside the same group — use only ToggleButtons in a ToggleButtonGroup. |
| Don't | Don't use a ToggleButton for on/off settings that persist across sessions — use a Switch instead, which better communicates "setting" semantics. |
Anatomy
| Element | Description | |
|---|---|---|
| Icon | A leading icon that represents the toggle action, like a star for favorite or bold "B" for formatting. | |
| Pressed icon | An alternate icon shown when pressed — typically a filled version of the default icon to reinforce the active state. | |
| Label | required | The visible text or accessible name. For icon-only toggles, used as the aria-label and auto-tooltip. |
| Spinner | Replaces the icon during async operations triggered by pressedChangeAction. |
Import
tsimport {XDSToggleButton} from '@xds/core/ToggleButton'
Props
| Prop | Type | Description |
|---|---|---|
labelrequired | string | Accessible label for the button. Used as visible text, or as aria-label for icon-only buttons. |
isPressed | boolean | Whether the button is currently pressed. Ignored when inside a group. |
onPressedChange | (isPressed: boolean) => void | Called when pressed state should change. Ignored when inside a group. |
pressedChangeAction | (isPressed: boolean) => Promise<void> | Async action handler for API-backed toggles. Shows loading spinner while pending. |
size | 'sm' | 'md' | 'lg' (default: 'md') | Button size. Defaults to group size when inside a group. |
isDisabled | boolean (default: false) | Whether the button is disabled. |
isLoading | boolean (default: false) | Whether the button shows a loading spinner. |
icon | ReactNode | Icon element. When provided without children, button becomes icon-only with tooltip from label. |
pressedIcon | ReactNode | Icon shown when pressed. Falls back to icon if not provided. |
children | ReactNode | Visible content. If omitted with icon, button becomes icon-only. |
tooltip | string | Tooltip text shown on hover. |
value | string | Value identifier when used inside XDSToggleButtonGroup. Required in groups. |
data-testid | string | Test selector for automated testing frameworks. |
Sub-components
ToggleButton is a compound component with 2 sub-components.XDSToggleButton
A button that toggles between pressed and unpressed states. Thin wrapper over XDSButton with controlled toggle pattern, icon swap, and font weight emphasis.| Prop | Type | Description |
|---|---|---|
labelrequired | string | Accessible label for the button. Used as visible text, or as aria-label for icon-only buttons. |
isPressed | boolean | Whether the button is currently pressed. Ignored when inside a group. |
onPressedChange | (isPressed: boolean) => void | Called when pressed state should change. Ignored when inside a group. |
pressedChangeAction | (isPressed: boolean) => Promise<void> | Async action handler for API-backed toggles. Shows loading spinner while pending. |
size | 'sm' | 'md' | 'lg' (default: 'md') | Button size. Defaults to group size when inside a group. |
isDisabled | boolean (default: false) | Whether the button is disabled. |
isLoading | boolean (default: false) | Whether the button shows a loading spinner. |
icon | ReactNode | Icon element. When provided without children, button becomes icon-only with tooltip from label. |
pressedIcon | ReactNode | Icon shown when pressed. Falls back to icon if not provided. |
children | ReactNode | Visible content. If omitted with icon, button becomes icon-only. |
tooltip | string | Tooltip text shown on hover. |
value | string | Value identifier when used inside XDSToggleButtonGroup. Required in groups. |
data-testid | string | Test selector for automated testing frameworks. |
XDSToggleButtonGroup
Groups toggle buttons for exclusive (single) or multi-select behavior. Uses discriminated union on type for type-safe value/onChange.| Prop | Type | Description |
|---|---|---|
childrenrequired | ReactNode | XDSToggleButton children. |
labelrequired | string | Accessible label for the group (aria-label). |
valuerequired | string | null | string[] | Currently selected value(s). Type depends on selection mode. |
onChangerequired | (value: string | null | string[]) => void | Called when selection changes. |
type | 'single' | 'multiple' (default: 'single') | Selection mode. Single allows one active button, multiple allows many. |
orientation | 'horizontal' | 'vertical' (default: 'horizontal') | Layout direction of the button group. |
size | 'sm' | 'md' | 'lg' (default: 'md') | Default size for buttons in the group. Individual buttons can override. |
isDisabled | boolean (default: false) | Whether all buttons in the group are disabled. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value. |
data-testid | string | Test selector for automated testing frameworks. |
Examples
Common configurations, variations, and states.ToggleButton — ColorToggle buttons with colored icons in the pressed state. Shows accent-colored toolbar formatting and semantic reaction colors (yellow star, red heart, blue bookmark).
tsx'use client';import {useState} from 'react';import {XDSToggleButton} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {XDSIcon} from '@xds/core/Icon';import {BoldIcon,ItalicIcon,UnderlineIcon,StrikethroughIcon,LinkIcon,StarIcon,BookmarkIcon,HeartIcon,BellIcon,} from '@heroicons/react/24/outline';import {BoldIcon as BoldSolid,ItalicIcon as ItalicSolid,UnderlineIcon as UnderlineSolid,StarIcon as StarSolid,BookmarkIcon as BookmarkSolid,HeartIcon as HeartSolid,} from '@heroicons/react/24/solid';export default function ToggleButtonColor() {const [toolbar, setToolbar] = useState<Record<string, boolean>>({bold: true,italic: false,underline: true,strikethrough: false,link: false,});const toggleToolbar = (key: string) =>setToolbar(prev => ({...prev, [key]: !prev[key]}));const [reactions, setReactions] = useState<Record<string, boolean>>({star: false,heart: false,bookmark: true,bell: false,});const toggleReaction = (key: string) =>setReactions(prev => ({...prev, [key]: !prev[key]}));return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Toolbar</XDSText><XDSStack direction="horizontal" gap={1}><XDSToggleButtonlabel="Bold"icon={<XDSIcon icon={BoldIcon} color="secondary" />}pressedIcon={<XDSIcon icon={BoldSolid} color="accent" />}isPressed={toolbar.bold}onPressedChange={() => toggleToolbar('bold')}isIconOnly/><XDSToggleButtonlabel="Italic"icon={<XDSIcon icon={ItalicIcon} color="secondary" />}pressedIcon={<XDSIcon icon={ItalicSolid} color="accent" />}isPressed={toolbar.italic}onPressedChange={() => toggleToolbar('italic')}isIconOnly/><XDSToggleButtonlabel="Underline"icon={<XDSIcon icon={UnderlineIcon} color="secondary" />}pressedIcon={<XDSIcon icon={UnderlineSolid} color="accent" />}isPressed={toolbar.underline}onPressedChange={() => toggleToolbar('underline')}isIconOnly/><XDSToggleButtonlabel="Strikethrough"icon={<XDSIcon icon={StrikethroughIcon} color="secondary" />}pressedIcon={<XDSIcon icon={StrikethroughIcon} color="accent" />}isPressed={toolbar.strikethrough}onPressedChange={() => toggleToolbar('strikethrough')}isIconOnly/><XDSToggleButtonlabel="Link"icon={<XDSIcon icon={LinkIcon} color="secondary" />}pressedIcon={<XDSIcon icon={LinkIcon} color="accent" />}isPressed={toolbar.link}onPressedChange={() => toggleToolbar('link')}isIconOnly/></XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Reactions</XDSText><XDSStack direction="horizontal" gap={2}><XDSToggleButtonlabel="Star"icon={<XDSIcon icon={StarIcon} color="secondary" />}pressedIcon={<XDSIcon icon={StarSolid} color="yellow" />}isPressed={reactions.star}onPressedChange={() => toggleReaction('star')}isIconOnly/><XDSToggleButtonlabel="Like"icon={<XDSIcon icon={HeartIcon} color="secondary" />}pressedIcon={<XDSIcon icon={HeartSolid} color="red" />}isPressed={reactions.heart}onPressedChange={() => toggleReaction('heart')}isIconOnly/><XDSToggleButtonlabel="Save"icon={<XDSIcon icon={BookmarkIcon} color="secondary" />}pressedIcon={<XDSIcon icon={BookmarkSolid} color="blue" />}isPressed={reactions.bookmark}onPressedChange={() => toggleReaction('bookmark')}isIconOnly/><XDSToggleButtonlabel="Follow"icon={<XDSIcon icon={BellIcon} color="secondary" />}pressedIcon={<XDSIcon icon={BellIcon} color="accent" />}isPressed={reactions.bell}onPressedChange={() => toggleReaction('bell')}isIconOnly/></XDSStack></XDSStack></XDSStack>);}
ToggleButton — GroupToggle button groups in single-select and multi-select modes. Single selection acts as a view mode switcher; multiple selection forms a formatting toolbar.
tsx'use client';import {useState} from 'react';import {XDSToggleButton, XDSToggleButtonGroup} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {XDSIcon} from '@xds/core/Icon';import {ListBulletIcon, Squares2X2Icon, TableCellsIcon} from '@heroicons/react/24/outline';import {BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon} from '@heroicons/react/24/outline';export default function ToggleButtonGroup() {const [view, setView] = useState<string | null>('list');const [formats, setFormats] = useState<string[]>(['bold']);return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Single selection</XDSText><XDSToggleButtonGroup value={view} onChange={setView} label="View mode"><XDSToggleButtonvalue="list"label="List view"icon={<XDSIcon icon={ListBulletIcon} />}isIconOnly/><XDSToggleButtonvalue="grid"label="Grid view"icon={<XDSIcon icon={Squares2X2Icon} />}isIconOnly/><XDSToggleButtonvalue="table"label="Table view"icon={<XDSIcon icon={TableCellsIcon} />}isIconOnly/></XDSToggleButtonGroup></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Multiple selections</XDSText><XDSToggleButtonGrouptype="multiple"value={formats}onChange={setFormats}label="Text formatting"><XDSToggleButtonvalue="bold"label="Bold"icon={<XDSIcon icon={BoldIcon} />}isIconOnly/><XDSToggleButtonvalue="italic"label="Italic"icon={<XDSIcon icon={ItalicIcon} />}isIconOnly/><XDSToggleButtonvalue="underline"label="Underline"icon={<XDSIcon icon={UnderlineIcon} />}isIconOnly/><XDSToggleButtonvalue="strikethrough"label="Strikethrough"icon={<XDSIcon icon={StrikethroughIcon} />}isIconOnly/></XDSToggleButtonGroup></XDSStack></XDSStack>);}
ToggleButton — Icon SwapIcon-only toggle buttons that swap between outline and solid icons when pressed. Use for actions like favorite, bookmark, or mute where the icon itself communicates the state.
tsx'use client';import {useState} from 'react';import {XDSToggleButton} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {XDSIcon} from '@xds/core/Icon';import {StarIcon as StarOutline, BookmarkIcon as BookmarkOutline, BellIcon, BellSlashIcon} from '@heroicons/react/24/outline';import {StarIcon as StarSolid, BookmarkIcon as BookmarkSolid} from '@heroicons/react/24/solid';export default function ToggleButtonIconSwap() {const [isFavorited, setIsFavorited] = useState(false);const [isBookmarked, setIsBookmarked] = useState(true);const [isMuted, setIsMuted] = useState(false);return (<XDSStack direction="vertical" gap={4}><XDSText type="supporting" color="secondary">Outline → solid icon swap on press</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={isFavorited}onPressedChange={setIsFavorited}isIconOnly/><XDSToggleButtonlabel="Bookmark"icon={<XDSIcon icon={BookmarkOutline} />}pressedIcon={<XDSIcon icon={BookmarkSolid} />}isPressed={isBookmarked}onPressedChange={setIsBookmarked}isIconOnly/><XDSToggleButtonlabel={isMuted ? 'Unmute notifications' : 'Mute notifications'}icon={<XDSIcon icon={BellIcon} />}pressedIcon={<XDSIcon icon={BellSlashIcon} />}isPressed={isMuted}onPressedChange={setIsMuted}isIconOnly/></XDSStack></XDSStack>);}
ToggleButton — LabelToggle buttons with visible text labels that show a font weight shift on press. Use when the icon alone is not enough to communicate the action.
tsx'use client';import {useState} from 'react';import {XDSToggleButton, XDSToggleButtonGroup} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {XDSIcon} from '@xds/core/Icon';import {EyeIcon, EyeSlashIcon, FunnelIcon, MapPinIcon} from '@heroicons/react/24/outline';export default function ToggleButtonLabel() {const [isVisible, setIsVisible] = useState(true);const [filters, setFilters] = useState<string[]>([]);return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Standalone with label and icon</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Visible"icon={<XDSIcon icon={EyeIcon} />}pressedIcon={<XDSIcon icon={EyeSlashIcon} />}isPressed={isVisible}onPressedChange={setIsVisible}>{isVisible ? 'Visible' : 'Hidden'}</XDSToggleButton></XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Labeled group — filter toolbar</XDSText><XDSToggleButtonGrouptype="multiple"value={filters}onChange={setFilters}label="Filters"><XDSToggleButtonvalue="filter"label="Filter"icon={<XDSIcon icon={FunnelIcon} />}>Filter</XDSToggleButton><XDSToggleButtonvalue="nearby"label="Nearby"icon={<XDSIcon icon={MapPinIcon} />}>Nearby</XDSToggleButton></XDSToggleButtonGroup></XDSStack></XDSStack>);}
ToggleButton — StatesDefault, pressed, disabled, and loading states of a standalone toggle button. Shows how visual treatment changes across states.
tsx'use client';import {XDSToggleButton} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {XDSIcon} from '@xds/core/Icon';import {StarIcon as StarOutline} from '@heroicons/react/24/outline';import {StarIcon as StarSolid} from '@heroicons/react/24/solid';export default function ToggleButtonStates() {return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Default</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={false}onPressedChange={() => {}}/><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={false}onPressedChange={() => {}}isIconOnly/></XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Pressed</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={true}onPressedChange={() => {}}/><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={true}onPressedChange={() => {}}isIconOnly/></XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Disabled</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}isPressed={false}onPressedChange={() => {}}isDisabled/><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}isPressed={false}onPressedChange={() => {}}isIconOnlyisDisabled/></XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Loading</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}isPressed={false}onPressedChange={() => {}}isLoading/><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}isPressed={false}onPressedChange={() => {}}isIconOnlyisLoading/></XDSStack></XDSStack></XDSStack>);}
Showcase source
tsx'use client';import {useState} from 'react';import {XDSToggleButton} from '@xds/core/ToggleButton';import {XDSStack} from '@xds/core/Layout';import {XDSIcon} from '@xds/core/Icon';import {StarIcon as StarOutline, BookmarkIcon as BookmarkOutline, BellIcon, BellSlashIcon} from '@heroicons/react/24/outline';import {StarIcon as StarSolid, BookmarkIcon as BookmarkSolid} from '@heroicons/react/24/solid';export default function ToggleButtonShowcase() {const [isFavorited, setIsFavorited] = useState(false);const [isBookmarked, setIsBookmarked] = useState(true);const [isMuted, setIsMuted] = useState(false);return (<XDSStack direction="horizontal" gap={3} vAlign="center"><XDSToggleButtonlabel="Favorite"icon={<XDSIcon icon={StarOutline} />}pressedIcon={<XDSIcon icon={StarSolid} />}isPressed={isFavorited}onPressedChange={setIsFavorited}isIconOnly/><XDSToggleButtonlabel="Bookmark"icon={<XDSIcon icon={BookmarkOutline} />}pressedIcon={<XDSIcon icon={BookmarkSolid} />}isPressed={isBookmarked}onPressedChange={setIsBookmarked}isIconOnly/><XDSToggleButtonlabel="Notifications"icon={<XDSIcon icon={BellIcon} />}pressedIcon={<XDSIcon icon={BellSlashIcon} />}isPressed={isMuted}onPressedChange={setIsMuted}>Notifications</XDSToggleButton></XDSStack>);}