apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Switch
Variant↳ other

Inputs

Switch

Binary on/off toggle. Hidden-input + sliding-thumb pattern. CSS-only slide (no Motion lib), 3 variants × 7 colors × 2 shapes × 3 sizes, async-friendly

Overview

<Switch /> is the binary on/off toggle. It uses the same hidden-input + custom-painted-indicator technique as <Checkbox />, but with a sliding thumb instead of a glyph. The visual language is intentionally different so users learn the affordance:

  • Switch flips a setting now (often async, often server-confirmed).
  • Checkbox queues a value for submission.

Switch ships three independently overridable slots: root, track, thumb. The slide is pure CSS (transition-transform) and respects prefers-reduced-motion — no Motion-library cost is paid for the most common interaction.

Overview — off, on, with description, and disabled

Loading preview…
Overview.tsx

Anatomy

text
[ track [ thumb ] | label / description column ]
[ track [ thumb ] | label / description column ]
  • track — the colored rail. Variant × color × shape lives here.
  • thumb — the sliding pill (always circular). Optional thumbIcon glyph inside.
  • label — children, click target via the wrapping <label>.
  • description — optional secondary text, wired via aria-describedby.

The track + thumb are decorative (aria-hidden). Assistive tech reads the underlying <input role="switch">.

Examples

Basic

Loading preview…
Basic.tsx

Variants

Loading preview…
Variants.tsx

Sizes

Loading preview…
Sizes.tsx

Colors

Loading preview…
Colors.tsx

Shapes

Loading preview…
Shapes.tsx

WithThumbIcons

Loading preview…
WithThumbIcons.tsx

WithDescription

Loading preview…
WithDescription.tsx

Disabled

Loading preview…
Disabled.tsx

Invalid

Loading preview…
Invalid.tsx

Loading

Loading preview…
Loading.tsx

SettingsRow

Loading preview…
SettingsRow.tsx

Controlled

Loading preview…
Controlled.tsx

Props

PropTypeDefaultDescription
checkedboolean—Controlled checked state.
colorResponsiveValue<SwitchColor>'primary'Palette role for the ON-state track / accent.
defaultCheckedbooleanfalseUncontrolled initial state.
descriptionReactNode—Secondary text below the label, wired via `aria-describedby`.
invalidboolean—Visual + a11y invalid state.
labelPositionenum'right'Logical label side. `right` = end side (LTR), start side (RTL).
loadingboolean—Async-toggle spinner inside the thumb. Sets `aria-busy` and blocks user toggling — useful for "Connect to Slack"-style settings where the server must confirm the new state.
onChange((event: ChangeEvent<HTMLInputElement, Element>) => void)—Native change handler. Fires alongside `onCheckedChange`.
onCheckedChange((checked: boolean) => void)—Canonical handler — receives the new boolean directly.
shapeenum'pill'Track corner shape. Thumb stays circular.
sizeResponsiveValue<SwitchSize>'md'Track length + thumb diameter + slide distance.
sxSx—Theme-aware inline style object.
thumbIconSwitchThumbIcon—Optional glyphs rendered inside the thumb per state.
variantResponsiveValue<SwitchVariant>'solid'Stylistic family of the ON-state.

Accessibility

  • The hidden <input type="checkbox" role="switch"> is the canonical control. Form participation (name + value), keyboard, and screen-reader announcements all flow through it.
  • aria-checked is binary (true / false) — switches are not tri-state.
  • aria-busy is set when loading is true; toggling is blocked at the React layer.
  • Focus ring lands on the track via the peer-focus-visible variant, colored to match the active color for every variant × color cell.
  • Space toggles; Enter is a no-op (matches native role="switch" per the ARIA spec).
  • invalid sets aria-invalid on the input and adds a danger ring on the track.
  • description is auto-wired via aria-describedby; consumer-supplied aria-describedby ids are merged in.
  • A dev-mode warning fires when no accessible name is provided (SWITCH_NO_LABEL).
  • The internal spinner sits inside the aria-hidden track. Screen readers learn about the loading state via the aria-busy on the input itself — the visible spinner is decorative.
  • axe-core passes across the variant × color × state matrix.

Theming

tsx
<ThemeProvider
  theme={defineTheme({
    components: {
      Switch: {
        defaultProps: { variant: 'soft', size: 'lg' },
        styleOverrides: {
          root: 'gap-3',
          track: 'shadow-inner',
          thumb: 'shadow-md',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>
<ThemeProvider
  theme={defineTheme({
    components: {
      Switch: {
        defaultProps: { variant: 'soft', size: 'lg' },
        styleOverrides: {
          root: 'gap-3',
          track: 'shadow-inner',
          thumb: 'shadow-md',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>

Instance-level overrides win via tailwind-merge:

tsx
<Switch className="font-medium" sx={{ radius: 'lg' }} style={{ accentColor: '#22c55e' }}>
  Override me
</Switch>
<Switch className="font-medium" sx={{ radius: 'lg' }} style={{ accentColor: '#22c55e' }}>
  Override me
</Switch>

Motion

  • Thumb slide — pure CSS transition-transform (~150ms, ease-standard).
  • Track bg crossfade — pure CSS transition-[background-color,border-color,...].
  • Loading spinner — Tailwind animate-spin on a 2-px ring; no Motion library import.
  • prefers-reduced-motion — collapses all transitions via Tailwind's motion-reduce variant on every animated rule.

Total Motion-library cost for Switch: 0 bytes. The whole component slides without paying for the spring engine.

RTL

  • labelPosition is logical (right = end side LTR, start side RTL).
  • The thumb starts at start-0.5 (logical), so it begins from the start edge in both directions.
  • The Tailwind translate-x-* slide is already direction-aware in modern browsers when the parent has dir="rtl" — the thumb visually slides toward the logical end without an explicit flip.

Do / Don't

  • Do use Switch for instant-effect settings: notifications, dark mode, integrations.
  • Do use loading for async toggles where the server must confirm the new state — the spinner inside the thumb is the right affordance for "I'm waiting on a server", not "I'm computing locally".
  • Do label the switch — short verb phrases ("Enable…", "Allow…") read best.
  • Don't use Switch inside a form that requires Submit before changes apply — that's <Checkbox />'s job. The user expects switches to take effect immediately.
  • Don't wrap multiple switches in a <RadioGroup>-style single-selection container — that's <Radio />. Switches are independent boolean toggles.
  • Don't use a switch for navigation or for opening a dialog — that's <Button>'s job. The switch metaphor implies persistent state.