DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

ToggleButton

A button that toggles between on and off states, with the same variant, intent, size, and rounded system as Button. Shows a persistent pressed visual when active.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { ToggleButton } from "@tessinaui/ui";
<ToggleButton>Star</ToggleButton>

Examples

Default

A controlled toggle that swaps its label and icon as it switches on and off.

Variants

Four visual styles — primary, secondary, ghost, and outline.

Sizes

Five text sizes from xs to xl on the family scale (32 / 40 / 48 / 56 / 96 px), plus icon — a 48 px square for an icon-only toggle that requires an accessible name. xs and sm keep a 44 px minimum touch target on phones.

Intents

Semantic color intents — none, error, warning, success, and info.

States

Off, pressed, disabled, and loading states.

On color

Use tone="on-color" so toggles read correctly on a colored surface.

API Reference

Props

PropTypeDefaultDescription
pressedboolean—Controlled pressed state
defaultPressedbooleanfalseInitial pressed state for uncontrolled usage
onPressedChange(pressed: boolean) => void—Callback fired when pressed state changes
variant"primary" | "secondary" | "ghost" | "outline""primary"Visual style
intent"none" | "error" | "warning" | "success" | "info""none"Semantic color intent
size"xs" | "sm" | "md" | "lg" | "xl" | "icon""md"Family scale 32 / 40 / 48 / 56 / 96 px; icon is a 48 px square and requires aria-label or aria-labelledby (enforced by the type)
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""full"Corner radius
tone"default" | "on-color"inherited from <Surface>High-contrast styling for a coloured or dark surface; pressed becomes the inverse chip
fullWidthbooleanfalseStretch to the container width
showBorderbooleantrueoutline only. outline means "border at rest", as on Button; pass false for a row of toggles that should read as one quiet surface until one is on — or use variant="ghost", which is what that look is
leadingIconReact.ReactNode—Icon rendered before the label. Stays mounted while loading (the spinner overlays it) so the width never moves
trailingIconReact.ReactNode—Icon rendered after the label. Stays mounted while loading
loadingbooleanfalseOverlays a spinner, sets aria-busy + aria-disabled, swallows activation. Keeps focus and size; not natively disabled
disabledbooleanfalseDisables the button and prevents toggling
dir"ltr" | "rtl""ltr"Text direction — RTL reverses icon/label order via CSS flex

Controlled vs Uncontrolled

Controlled — manage pressed state externally:

const [starred, setStarred] = useState(false);

<ToggleButton pressed={starred} onPressedChange={setStarred}>
  {starred ? "Starred" : "Star"}
</ToggleButton>

Uncontrolled — let the component manage its own state:

<ToggleButton defaultPressed={false} onPressedChange={(p) => console.log(p)}>
  Star
</ToggleButton>

Pressed intents

Accessibility

Follows the WAI-ARIA Button pattern, toggle variant.

  • Renders a <button> with aria-pressed reflecting the current toggle state
  • data-pressed is present (empty string) when pressed and absent when not — target it as [data-pressed], not [data-pressed="true"]. A [data-pressed="false"] selector never matches anything
  • Keyboard accessible: Enter and Space toggle the button
  • Focus ring visible with 2px ring + 2px offset (matches design system standard)
  • Disabled state prevents toggling and is reflected via the native disabled attribute
  • loading is not the same as disabled: the toggle keeps focus and reports aria-busy, and both icons stay mounted so its width does not move
  • Under Windows high-contrast mode the pressed state maps to the system Highlight colours — the painted fill is stripped there, which would otherwise leave an icon-only toggle with no pressed indication at all
  • size="icon" requires aria-label or aria-labelledby, enforced by the type
  • Touch targets ≥ 44×44px on phones and tablets (xs/sm grow; md+ already clear it)

When to use which

  • A toggle applies immediately. Bold in a toolbar, mute, a filter chip. If the change needs saving, it is a form control, not a toggle.
  • For a persistent on/off setting, use Switch — a toggle button reads as "apply this now", a switch as "this is how it stays".
  • For one-of-many, use ToggleGroup or SegmentedControl, which manage selection and roving focus for you.
  • Keep the label identical in both states. "Star" that becomes "Starred" changes width mid-click and makes the pressed state harder, not easier, to read — the state is already carried by aria-pressed and the fill.
ToastToggleGroup

On this page

PlaygroundInstallationUsageExamplesDefaultVariantsSizesIntentsStatesOn colorAPI ReferencePropsControlled vs UncontrolledPressed intentsAccessibilityWhen to use which