XDSToggleButton@xds/core · ToggleButton
Preview coming soon

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

GuidancePractices
DoUse 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.
DoKeep the label identical between pressed and unpressed states. Let the visual treatment (icon, weight, background) communicate the change.
DoWrap related toggles in a ToggleButtonGroup with an accessible label so screen readers announce them as a connected set.
Don'tDon't use a ToggleButton for one-time actions like "Submit" or "Delete" — those are regular Buttons, not toggles.
Don'tDon't mix ToggleButtons with regular Buttons inside the same group — use only ToggleButtons in a ToggleButtonGroup.
Don'tDon't use a ToggleButton for on/off settings that persist across sessions — use a Switch instead, which better communicates "setting" semantics.

Anatomy

ElementDescription
IconA leading icon that represents the toggle action, like a star for favorite or bold "B" for formatting.
Pressed iconAn alternate icon shown when pressed — typically a filled version of the default icon to reinforce the active state.
LabelrequiredThe visible text or accessible name. For icon-only toggles, used as the aria-label and auto-tooltip.
SpinnerReplaces the icon during async operations triggered by pressedChangeAction.

Import

ts
import {XDSToggleButton} from '@xds/core/ToggleButton'

Props

PropTypeDescription
labelrequired
stringAccessible label for the button. Used as visible text, or as aria-label for icon-only buttons.
isPressed
booleanWhether the button is currently pressed. Ignored when inside a group.
onPressedChange
(isPressed: boolean) => voidCalled 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
ReactNodeIcon element. When provided without children, button becomes icon-only with tooltip from label.
pressedIcon
ReactNodeIcon shown when pressed. Falls back to icon if not provided.
children
ReactNodeVisible content. If omitted with icon, button becomes icon-only.
tooltip
stringTooltip text shown on hover.
value
stringValue identifier when used inside XDSToggleButtonGroup. Required in groups.
data-testid
stringTest 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.
PropTypeDescription
labelrequired
stringAccessible label for the button. Used as visible text, or as aria-label for icon-only buttons.
isPressed
booleanWhether the button is currently pressed. Ignored when inside a group.
onPressedChange
(isPressed: boolean) => voidCalled 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
ReactNodeIcon element. When provided without children, button becomes icon-only with tooltip from label.
pressedIcon
ReactNodeIcon shown when pressed. Falls back to icon if not provided.
children
ReactNodeVisible content. If omitted with icon, button becomes icon-only.
tooltip
stringTooltip text shown on hover.
value
stringValue identifier when used inside XDSToggleButtonGroup. Required in groups.
data-testid
stringTest 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.
PropTypeDescription
childrenrequired
ReactNodeXDSToggleButton children.
labelrequired
stringAccessible label for the group (aria-label).
valuerequired
string | null | string[]Currently selected value(s). Type depends on selection mode.
onChangerequired
(value: string | null | string[]) => voidCalled 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
StyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value.
data-testid
stringTest 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}>
<XDSToggleButton
label="Bold"
icon={<XDSIcon icon={BoldIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={BoldSolid} color="accent" />}
isPressed={toolbar.bold}
onPressedChange={() => toggleToolbar('bold')}
isIconOnly
/>
<XDSToggleButton
label="Italic"
icon={<XDSIcon icon={ItalicIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={ItalicSolid} color="accent" />}
isPressed={toolbar.italic}
onPressedChange={() => toggleToolbar('italic')}
isIconOnly
/>
<XDSToggleButton
label="Underline"
icon={<XDSIcon icon={UnderlineIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={UnderlineSolid} color="accent" />}
isPressed={toolbar.underline}
onPressedChange={() => toggleToolbar('underline')}
isIconOnly
/>
<XDSToggleButton
label="Strikethrough"
icon={<XDSIcon icon={StrikethroughIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={StrikethroughIcon} color="accent" />}
isPressed={toolbar.strikethrough}
onPressedChange={() => toggleToolbar('strikethrough')}
isIconOnly
/>
<XDSToggleButton
label="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}>
<XDSToggleButton
label="Star"
icon={<XDSIcon icon={StarIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={StarSolid} color="yellow" />}
isPressed={reactions.star}
onPressedChange={() => toggleReaction('star')}
isIconOnly
/>
<XDSToggleButton
label="Like"
icon={<XDSIcon icon={HeartIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={HeartSolid} color="red" />}
isPressed={reactions.heart}
onPressedChange={() => toggleReaction('heart')}
isIconOnly
/>
<XDSToggleButton
label="Save"
icon={<XDSIcon icon={BookmarkIcon} color="secondary" />}
pressedIcon={<XDSIcon icon={BookmarkSolid} color="blue" />}
isPressed={reactions.bookmark}
onPressedChange={() => toggleReaction('bookmark')}
isIconOnly
/>
<XDSToggleButton
label="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">
<XDSToggleButton
value="list"
label="List view"
icon={<XDSIcon icon={ListBulletIcon} />}
isIconOnly
/>
<XDSToggleButton
value="grid"
label="Grid view"
icon={<XDSIcon icon={Squares2X2Icon} />}
isIconOnly
/>
<XDSToggleButton
value="table"
label="Table view"
icon={<XDSIcon icon={TableCellsIcon} />}
isIconOnly
/>
</XDSToggleButtonGroup>
</XDSStack>
<XDSStack direction="vertical" gap={1}>
<XDSText type="supporting" color="secondary">
Multiple selections
</XDSText>
<XDSToggleButtonGroup
type="multiple"
value={formats}
onChange={setFormats}
label="Text formatting">
<XDSToggleButton
value="bold"
label="Bold"
icon={<XDSIcon icon={BoldIcon} />}
isIconOnly
/>
<XDSToggleButton
value="italic"
label="Italic"
icon={<XDSIcon icon={ItalicIcon} />}
isIconOnly
/>
<XDSToggleButton
value="underline"
label="Underline"
icon={<XDSIcon icon={UnderlineIcon} />}
isIconOnly
/>
<XDSToggleButton
value="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">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
pressedIcon={<XDSIcon icon={StarSolid} />}
isPressed={isFavorited}
onPressedChange={setIsFavorited}
isIconOnly
/>
<XDSToggleButton
label="Bookmark"
icon={<XDSIcon icon={BookmarkOutline} />}
pressedIcon={<XDSIcon icon={BookmarkSolid} />}
isPressed={isBookmarked}
onPressedChange={setIsBookmarked}
isIconOnly
/>
<XDSToggleButton
label={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">
<XDSToggleButton
label="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>
<XDSToggleButtonGroup
type="multiple"
value={filters}
onChange={setFilters}
label="Filters">
<XDSToggleButton
value="filter"
label="Filter"
icon={<XDSIcon icon={FunnelIcon} />}>
Filter
</XDSToggleButton>
<XDSToggleButton
value="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">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
pressedIcon={<XDSIcon icon={StarSolid} />}
isPressed={false}
onPressedChange={() => {}}
/>
<XDSToggleButton
label="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">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
pressedIcon={<XDSIcon icon={StarSolid} />}
isPressed={true}
onPressedChange={() => {}}
/>
<XDSToggleButton
label="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">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
isPressed={false}
onPressedChange={() => {}}
isDisabled
/>
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
isPressed={false}
onPressedChange={() => {}}
isIconOnly
isDisabled
/>
</XDSStack>
</XDSStack>
<XDSStack direction="vertical" gap={1}>
<XDSText type="supporting" color="secondary">
Loading
</XDSText>
<XDSStack direction="horizontal" gap={3} vAlign="center">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
isPressed={false}
onPressedChange={() => {}}
isLoading
/>
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
isPressed={false}
onPressedChange={() => {}}
isIconOnly
isLoading
/>
</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">
<XDSToggleButton
label="Favorite"
icon={<XDSIcon icon={StarOutline} />}
pressedIcon={<XDSIcon icon={StarSolid} />}
isPressed={isFavorited}
onPressedChange={setIsFavorited}
isIconOnly
/>
<XDSToggleButton
label="Bookmark"
icon={<XDSIcon icon={BookmarkOutline} />}
pressedIcon={<XDSIcon icon={BookmarkSolid} />}
isPressed={isBookmarked}
onPressedChange={setIsBookmarked}
isIconOnly
/>
<XDSToggleButton
label="Notifications"
icon={<XDSIcon icon={BellIcon} />}
pressedIcon={<XDSIcon icon={BellSlashIcon} />}
isPressed={isMuted}
onPressedChange={setIsMuted}>
Notifications
</XDSToggleButton>
</XDSStack>
);
}