All components
Switch
On/off toggles with a sliding thumb in solid and raised track variants.
123456789101112131415import { Switch } from "#components/ui/ark-ui/switch.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
export function Example() { return ( // variant/size resolve once on Root; Control renders the Thumb by default <Switch.Root defaultChecked variant="solid" size="md" palette={indigoPalette}> <Switch.Control /> <Switch.Label>Auto-save</Switch.Label> <Switch.HiddenInput /> </Switch.Root> );}
// controlled: <Switch.Root checked={checked} onCheckedChange={(d) => setChecked(d.checked)}>Variants
solid
raised
Sizes
xs
sm
md
lg
Install
1npx stylexblocks add switchSource
components/ui/ark-ui/switch.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266import * as stylex from "@stylexjs/stylex";import { Switch as ArkSwitch, useSwitchContext } from "@ark-ui/react/switch";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, resolveSlotStyles } from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { colors } from '@stylexblocks/ui/tokens/colors.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { durations } from '@stylexblocks/ui/tokens/durations.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticShadows } from '@stylexblocks/ui/semantic-tokens/shadows.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const switchRoot = stylex.create({ base: { display: 'inline-flex', gap: spacing._2_5, alignItems: 'center', position: 'relative', verticalAlign: 'middle', '--switch-diff': 'calc(var(--switch-width) - var(--switch-height))', '--switch-x': 'calc(var(--switch-width) - var(--switch-height))', },})
const switchRootSizes = stylex.create({ xs: { '--switch-width': sizes._6, '--switch-height': sizes._3, '--switch-indicator-font-size': fontSizes.xs, }, sm: { '--switch-width': sizes._8, '--switch-height': sizes._4, '--switch-indicator-font-size': fontSizes.xs, }, md: { '--switch-width': sizes._10, '--switch-height': sizes._5, '--switch-indicator-font-size': fontSizes.sm, }, lg: { '--switch-width': sizes._12, '--switch-height': sizes._6, '--switch-indicator-font-size': fontSizes.md, },})
const switchLabel = stylex.create({ base: { lineHeight: 1, userSelect: 'none', fontSize: fontSizes.sm, fontWeight: fontWeights.medium, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const switchIndicator = stylex.create({ base: { position: 'absolute', height: 'var(--switch-height)', width: 'var(--switch-height)', fontSize: 'var(--switch-indicator-font-size)', fontWeight: fontWeights.medium, flexShrink: 0, userSelect: 'none', display: 'grid', placeContent: 'center', transition: 'inset-inline-start 0.12s ease', insetInlineStart: { default: 'calc(var(--switch-x) - 2px)', ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': '2px', }, },})
const switchControl = stylex.create({ base: { display: 'inline-flex', gap: '0.5rem', flexShrink: 0, justifyContent: 'flex-start', cursor: { default: cursor.switch, ':is(:disabled, [disabled], [data-disabled])': 'not-allowed', }, borderRadius: radii.full, position: 'relative', width: 'var(--switch-width)', height: 'var(--switch-height)', opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, '--focus-ring-color': { default: null, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderError, }, },})
const switchControlVariants = stylex.create({ solid: { borderRadius: radii.full, backgroundColor: { default: semanticColors.bgEmphasized, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, }, raised: { borderRadius: radii.full, height: 'calc(var(--switch-height) / 2)', backgroundColor: { default: semanticColors.bgMuted, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': `color-mix(in oklch, ${colorPalette.solid} 60%, transparent)`, }, boxShadow: semanticShadows.inset, },})
const switchThumb = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transitionProperty: 'translate', transitionDuration: durations.fast, borderRadius: 'inherit', translate: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': 'var(--switch-x) 0', }, },})
const switchThumbVariants = stylex.create({ solid: { backgroundColor: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.contrast, }, width: 'var(--switch-height)', height: 'var(--switch-height)', scale: 0.8, boxShadow: semanticShadows.sm, }, raised: { width: 'var(--switch-height)', height: 'var(--switch-height)', position: 'relative', top: 'calc(var(--switch-height) * -0.25)', backgroundColor: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, boxShadow: semanticShadows.xs, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, },})
export type SwitchVariant = keyof typeof switchControlVariants
export type SwitchSize = keyof typeof switchRootSizes
const switchRecipe = defineSlotRecipe({ slots: { root: { styles: switchRoot, sizes: switchRootSizes }, label: { styles: switchLabel }, control: { styles: switchControl, variants: switchControlVariants }, thumb: { styles: switchThumb, variants: switchThumbVariants }, }, defaultVariants: { variant: "solid", size: "md" },});
const chrome = stylex.create({ cursorPointer: { cursor: "pointer" },});
function ControlNode({ children, ...props}: React.ComponentProps<typeof ArkSwitch.Control>) { const machine = useSwitchContext(); return ( <ArkSwitch.Control {...props} data-checked={machine.checked ? "" : undefined}> {children ?? <Thumb />} </ArkSwitch.Control> );}
function ThumbNode(props: React.ComponentProps<typeof ArkSwitch.Thumb>) { const machine = useSwitchContext(); return <ArkSwitch.Thumb {...props} data-checked={machine.checked ? "" : undefined} />;}
const { withProvider, withContext } = createSlotRecipeContext(switchRecipe);
const Thumb = withContext(ThumbNode, "thumb", "SwitchThumb");
/** * Switch — Ark UI's switch machine wearing the switch slot recipe. * `<Switch.Root variant size>` resolves the recipe once; every part * inherits it through context. Control and Thumb copy `data-checked` from * the machine so the recipe's checked selectors apply. */export const Switch = { Root: withProvider( ArkSwitch.Root, (v) => [...resolveSlotStyles(switchRecipe.slots.root, v), chrome.cursorPointer], "SwitchRoot", ), Control: withContext(ControlNode, "control", "SwitchControl"), Thumb, Label: withContext(ArkSwitch.Label, "label", "SwitchLabel"), HiddenInput: ArkSwitch.HiddenInput,};
export type SwitchRootProps = React.ComponentProps<typeof Switch.Root>;export type SwitchControlProps = React.ComponentProps<typeof Switch.Control>;export type SwitchThumbProps = React.ComponentProps<typeof Switch.Thumb>;export type SwitchLabelProps = React.ComponentProps<typeof Switch.Label>;
Examples
Installable blocks built with Switch — real use cases, delivered as source you own.
Model Picker
Settings menu for choosing an inference model — Fast switch, Effort submenu, and a searchable combobox for the model list.