moduix

Switch

A composable switch control for turning a setting on or off.

API Reference

Original primitive API

Behavior, accessibility details, and low-level props are documented by Base UI.

Base UI API

Basic

import { Switch, SwitchField, SwitchLabel } from "moduix";export function SwitchDemo() {  return (    <SwitchField>      <Switch defaultChecked />      <SwitchLabel>Enable notifications</SwitchLabel>    </SwitchField>  );}

Full list of component variables available for project-level overrides.

PropertyDefaultDescription
--switch-bgvar(--color-muted)Controls unchecked background color.
--switch-bg-checkedvar(--color-primary)Controls checked background color.
--switch-bg-checked-hovervar(--switch-bg-checked, var(--color-primary))Controls checked hover background color.
--switch-bg-hovervar(--color-accent)Controls unchecked hover background color.
--switch-border-colorvar(--color-border)Controls unchecked border color.
--switch-border-color-checkedvar(--color-primary)Controls checked border color.
--switch-border-widthvar(--border-width-sm)Controls switch border width.
--switch-disabled-opacityvar(--opacity-disabled)Controls disabled opacity.
--switch-focus-ring-colorvar(--color-ring)Controls focus ring color.
--switch-focus-ring-offsetvar(--border-width-sm)Controls focus ring offset.
--switch-focus-ring-widthvar(--border-width-sm)Controls focus ring width.
--switch-gapvar(--spacing-2)Controls spacing between switch and label.
--switch-height-xs1remControls switch height for the xs size.
--switch-height-sm1.25remControls switch height for the sm size.
--switch-height-md1.5remControls switch height for the md size.
--switch-height-lg1.75remControls switch height for the lg size.
--switch-height-xl2remControls switch height for the xl size.
--switch-label-colorvar(--color-foreground)Controls label text color.
--switch-label-font-sizevar(--text-sm)Controls label font size.
--switch-label-font-weightvar(--weight-medium)Controls label font weight.
--switch-label-line-heightvar(--line-height-text-sm)Controls label line height.
--switch-padding0.125remControls inner switch padding.
--switch-radiusvar(--radius-full)Controls switch corner radius.
--switch-thumb-bgvar(--color-background)Controls thumb background color for both states.
--switch-thumb-bg-uncheckedvar(--switch-thumb-bg, var(--color-background))Controls unchecked thumb background color.
--switch-thumb-bg-checkedvar(--switch-thumb-bg, var(--color-primary-foreground))Controls checked thumb background color.
--switch-thumb-border-colortransparentControls thumb border color.
--switch-thumb-border-width0Controls thumb border width.
--switch-thumb-colorvar(--color-muted)Controls thumb content color.
--switch-thumb-color-uncheckedvar(--switch-thumb-color, var(--color-muted))Controls unchecked thumb content color.
--switch-thumb-color-checkedvar(--switch-thumb-color, var(--color-primary))Controls checked thumb content color.
--switch-thumb-icon-size65%Controls custom thumb icon size.
--switch-thumb-radiusvar(--radius-full)Controls thumb corner radius.
--switch-thumb-shadowvar(--shadow-sm)Controls thumb shadow.
--switch-thumb-size-xs0.625remControls thumb size for the xs switch size.
--switch-thumb-size-sm0.875remControls thumb size for the sm switch size.
--switch-thumb-size-md1.125remControls thumb size for the md switch size.
--switch-thumb-size-lg1.375remControls thumb size for the lg switch size.
--switch-thumb-size-xl1.625remControls thumb size for the xl switch size.
--switch-thumb-transitionvar(--switch-transition, var(--transition-default))Controls thumb movement transition timing.
--switch-thumb-translatevar(--switch-thumb-translate-default)Controls checked thumb translation distance.
--switch-transitionvar(--transition-default)Controls state transition timing.
--switch-width-xs1.75remControls switch width for the xs size.
--switch-width-sm2remControls switch width for the sm size.
--switch-width-md2.5remControls switch width for the md size.
--switch-width-lg3remControls switch width for the lg size.
--switch-width-xl3.5remControls switch width for the xl size.

Interactive variables scoped for docs preview without changing size scale tokens.

PropertyValueDefaultDescription
--switch-bgvar(--color-muted)Controls unchecked background color.
--switch-bg-checkedvar(--color-primary)Controls checked background color.
--switch-bg-hovervar(--color-accent)Controls unchecked hover background color.
--switch-border-colorvar(--color-border)Controls unchecked border color.
--switch-border-color-checkedvar(--color-primary)Controls checked border color.
--switch-focus-ring-colorvar(--color-ring)Controls focus ring color.
--switch-thumb-bgvar(--color-background)Controls thumb background color.
--switch-thumb-colorvar(--color-muted)Controls thumb content color.
--switch-label-colorvar(--color-foreground)Controls label text color.

Anatomy

Switch renders the interactive control and its internal thumb automatically. Use SwitchField and SwitchLabel when the label wraps the control; use a native label with id when the label is a sibling.

SwitchField
|- Switch
|  `- internal thumb
`- SwitchLabel
<SwitchField>
  <Switch defaultChecked />
  <SwitchLabel>Enable notifications</SwitchLabel>
</SwitchField>
PartRole
SwitchRoot state machine and visible track. Controls checked, defaultChecked, form props, and size.
internal thumbMovable visual marker rendered by Switch. Customize content with thumb and styles with classNames.thumb.
SwitchFieldOptional wrapping label for the common inline field pattern.
SwitchLabelOptional text label slot styled to match the switch field.

Switch does not use portal-like service layers such as portal, positioner, backdrop, or viewport. In most cases, keep the internal thumb and style the root Switch, SwitchField, and SwitchLabel.

Composition

Use Switch directly for the control. Pass className to style the track and classNames.thumb to style the internal thumb. Pass thumb when the thumb needs custom content, such as an icon.

Use nativeButton with render={<button />} for sibling label patterns. Use the render callback when a native button switch needs to live inside a wrapping label without invalid nested button markup.

Examples

Sizes

Use size to align the switch with different form densities. Override --switch-width-{size}, --switch-height-{size}, and --switch-thumb-size-{size} when a specific size needs custom dimensions.

import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function SwitchSizesDemo() {  return (    <div className={styles.stack}>      <SwitchField>        <Switch size="xs" defaultChecked />        <SwitchLabel>Extra-small</SwitchLabel>      </SwitchField>      <SwitchField>        <Switch size="sm" defaultChecked />        <SwitchLabel>Small</SwitchLabel>      </SwitchField>      <SwitchField>        <Switch size="md" defaultChecked />        <SwitchLabel>Medium</SwitchLabel>      </SwitchField>      <SwitchField>        <Switch size="lg" defaultChecked />        <SwitchLabel>Large</SwitchLabel>      </SwitchField>      <SwitchField>        <Switch size="xl" defaultChecked />        <SwitchLabel>Extra-large</SwitchLabel>      </SwitchField>    </div>  );}
.stack {  display: flex;  flex-direction: column;  align-items: flex-start;  gap: var(--spacing-3);}

Disabled

Use disabled to prevent interaction while preserving the current setting state in the UI.

import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function DisabledSwitchDemo() {  return (    <div className={styles.stack}>      <SwitchField>        <Switch disabled />        <SwitchLabel>Enable dark mode</SwitchLabel>      </SwitchField>      <SwitchField>        <Switch defaultChecked disabled />        <SwitchLabel>Keep me signed in</SwitchLabel>      </SwitchField>    </div>  );}
.stack {  display: flex;  flex-direction: column;  align-items: flex-start;  gap: var(--spacing-3);}

Controlled

Control checked from React state when the switch needs to coordinate with other UI.

Current value: true
import { Switch, SwitchField, SwitchLabel } from "moduix";import { useState } from "react";import styles from "./switch-demo.module.css";export function ControlledSwitchDemo() {  const [checked, setChecked] = useState(true);  return (    <div className={styles.stack}>      <SwitchField>        <Switch checked={checked} onCheckedChange={setChecked} />        <SwitchLabel>{checked ? "On" : "Off"}</SwitchLabel>      </SwitchField>      <span className={styles.hint}>Current value: {String(checked)}</span>    </div>  );}
.stack {  display: flex;  flex-direction: column;  align-items: flex-start;  gap: var(--spacing-3);}.hint {  color: var(--color-muted-foreground);  font-size: var(--text-xs);  line-height: var(--line-height-text-xs);}

Custom Icon

Pass thumb to render any icon from your application or icon library inside the internal thumb.

import { Switch, SwitchField, SwitchLabel } from "moduix";import type { ComponentProps } from "react";import styles from "./switch-demo.module.css";function PowerIcon(props: ComponentProps<"svg">) {  return (    <svg viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false" {...props}>      <path        d="M8 2.5V7M5.1 4.3A5 5 0 1 0 10.9 4.3"        stroke="currentColor"        strokeWidth="1.8"        strokeLinecap="round"      />    </svg>  );}export function CustomIconSwitchDemo() {  return (    <SwitchField>      <Switch        defaultChecked        thumb={<PowerIcon />}        classNames={{ thumb: styles.customIconThumb }}      />      <SwitchLabel>Use custom thumb icon</SwitchLabel>    </SwitchField>  );}
.customIconThumb {  --switch-thumb-color-checked: var(--color-primary);  --switch-thumb-color-unchecked: var(--color-muted-foreground);  --switch-thumb-icon-size: 0.75rem;}

Class Names

Pass className to the field, root, and label slots when styling with CSS Modules, Tailwind CSS, or CSS-in-JS.

import { Switch, SwitchField, SwitchLabel } from "moduix";import styles from "./switch-demo.module.css";export function StyledSwitchDemo() {  return (    <SwitchField className={styles.customField}>      <Switch className={styles.customSwitch} defaultChecked />      <SwitchLabel className={styles.customLabel}>Styled with className</SwitchLabel>    </SwitchField>  );}
.customField {  gap: var(--spacing-3);}.customSwitch {  --switch-bg: color-mix(in oklab, var(--color-primary) 12%, transparent);  --switch-bg-hover: color-mix(in oklab, var(--color-primary) 18%, transparent);  --switch-bg-checked: var(--color-primary);  --switch-border-color: color-mix(in oklab, var(--color-primary) 45%, var(--color-border));}.customLabel {  color: var(--color-primary);}

Sibling Label

Use nativeButton with render={<button />} for the sibling label pattern with htmlFor and id.

import { Switch } from "moduix";import { useId } from "react";import styles from "./switch-demo.module.css";export function SiblingLabelSwitchDemo() {  const id = useId();  return (    <div className={styles.siblingRow}>      <Switch nativeButton render={<button />} id={id} defaultChecked />      <label htmlFor={id} className={styles.label}>        Receive product updates      </label>    </div>  );}
.siblingRow {  display: flex;  align-items: center;  gap: var(--switch-gap, var(--spacing-2));}.label {  color: var(--switch-label-color, var(--color-foreground));  font-size: var(--switch-label-font-size, var(--text-sm));  font-weight: var(--switch-label-font-weight, var(--weight-medium));  line-height: var(--switch-label-line-height, var(--line-height-text-sm));}

Native Button Render Callback

Use the render callback when a native button switch needs to be wrapped by a label without producing invalid nested button markup.

import { Switch } from "moduix";import styles from "./switch-demo.module.css";export function NativeButtonSwitchDemo() {  return (    <Switch      defaultChecked      nativeButton      render={(buttonProps) => (        <label className={styles.siblingRow}>          <button {...buttonProps} />          <span className={styles.label}>Enable reminders</span>        </label>      )}    />  );}
.siblingRow {  display: flex;  align-items: center;  gap: var(--switch-gap, var(--spacing-2));}.label {  color: var(--switch-label-color, var(--color-foreground));  font-size: var(--switch-label-font-size, var(--text-sm));  font-weight: var(--switch-label-font-weight, var(--weight-medium));  line-height: var(--switch-label-line-height, var(--line-height-text-sm));}

Form Integration

Use Field when the switch participates in form labeling, validation, or submission.

import {  Field,  FieldLabel,  Switch,  SwitchLabel,} from "moduix";export function SwitchFormDemo() {  return (    <Field name="notifications">      <FieldLabel>        <Switch defaultChecked />        <SwitchLabel>Notifications</SwitchLabel>      </FieldLabel>    </Field>  );}

On this page