Switch

A switch is a simple, two-option control that can be toggled between two states — on and off. It's commonly used in settings or preferences menus where users can enable or disable features.

import {Switch} from "@qualcomm-ui/react/switch"

Examples

Simple

Basic switch using the simple API with a label.

<Switch label="Switch" />

Composite

Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.

<Switch.Root>
  <Switch.HiddenInput />
  <Switch.Label>Label</Switch.Label>
  <Switch.Control />
</Switch.Root>

States

Based on the inputs, the switch will render as checked or unchecked.

<Switch />
<Switch defaultChecked />

Disabled State

When disabled is true, the switch becomes non-interactive and is rendered with reduced opacity to indicate its unavailable state.

<Switch disabled label="Disabled" />

Sizes

Use the size prop to change the size of the switch.

<Switch label="Small (sm)" size="sm" />
<Switch label="Medium (md)" size="md" /> {/* default */}

Controlled State

Set the initial value using the defaultChecked prop, or use checked and onCheckedChange to control the value manually. These props follow our controlled state pattern.

import {type ReactElement, useState} from "react"

import {Switch} from "@qualcomm-ui/react/switch"

export default function SwitchControlledDemo(): ReactElement {
  const [checked, setChecked] = useState<boolean>(false)

  return (
    <Switch.Root
      checked={checked}
      onCheckedChange={(nextState) => {
        console.log("Switch state change:", nextState)
        setChecked(nextState)
      }}
    >
      <Switch.HiddenInput />
      <Switch.Control />
      <Switch.Label>Label</Switch.Label>
    </Switch.Root>
  )
}

Forms

Choose the form library that fits your needs—we've built examples with React Hook Form and Tanstack Form to get you started.

React Hook Form

Use React Hook Form to handle the switch state and validation. ArkType works great for schema validation if you need it.

import type {ReactElement} from "react"

import {arktypeResolver} from "@hookform/resolvers/arktype"
import {type} from "arktype"
import {Controller, useForm} from "react-hook-form"

import {Button} from "@qualcomm-ui/react/button"
import {Switch} from "@qualcomm-ui/react/switch"

interface FormData {
  acceptTerms: boolean
  newsletter: boolean
}

const acceptTermsSchema = type("boolean")
  .narrow((value: boolean) => value === true)
  .configure({
    message: "Please accept the Terms of Service to continue",
  })
const FormSchema = type({
  // must be true
  acceptTerms: acceptTermsSchema,
  // can be true or false
  newsletter: "boolean",
})

export default function SwitchReactHookFormDemo(): ReactElement {
  const {control, handleSubmit} = useForm<FormData>({
    defaultValues: {
      acceptTerms: false,
      newsletter: true,
    },
    resolver: arktypeResolver(FormSchema),
  })

  return (
    <form
      className="flex w-56 flex-col gap-2"
      onSubmit={(e) => {
        void handleSubmit((data) => console.log(data))(e)
      }}
    >
      <Controller
        control={control}
        name="newsletter"
        render={({field: {name, onChange, value, ...fieldProps}}) => {
          return (
            <Switch.Root
              checked={value}
              name={name}
              onCheckedChange={onChange}
              {...fieldProps}
            >
              <Switch.HiddenInput />
              <Switch.Control />
              <Switch.Label>Subscribe to our Newsletter</Switch.Label>
            </Switch.Root>
          )
        }}
      />

      <Controller
        control={control}
        name="acceptTerms"
        render={({
          field: {onChange, value, ...fieldProps},
          fieldState: {error},
        }) => {
          return (
            <Switch.Root
              checked={value}
              invalid={!!error?.message}
              onCheckedChange={onChange}
              {...fieldProps}
            >
              <Switch.HiddenInput />
              <Switch.Control />
              <Switch.Label>Accept Terms of Service</Switch.Label>
              <Switch.ErrorText>{error?.message}</Switch.ErrorText>
            </Switch.Root>
          )
        }}
      />

      <Button
        className="mt-1"
        emphasis="primary"
        size="sm"
        type="submit"
        variant="fill"
      >
        Submit
      </Button>
    </form>
  )
}

Tanstack Form

Tanstack Form handles switch validation with its built-in validators.

import type {ReactElement} from "react"

import {useForm} from "@tanstack/react-form"

import {Button} from "@qualcomm-ui/react/button"
import {Switch} from "@qualcomm-ui/react/switch"

interface FormData {
  acceptTerms: boolean
  newsletter: boolean
}

const defaultFormData: FormData = {
  acceptTerms: false,
  newsletter: true,
}

const errorMessage = "Please accept the Terms of Service to continue"

export default function SwitchTanstackFormDemo(): ReactElement {
  const form = useForm({
    defaultValues: defaultFormData,
    onSubmit: async ({value}) => {
      // Do something with form data
      console.log(value)
    },
  })

  return (
    <form
      className="flex w-56 flex-col gap-2"
      onSubmit={(event) => {
        event.preventDefault()
        event.stopPropagation()
        void form.handleSubmit()
      }}
    >
      <form.Field name="newsletter">
        {({handleChange, name, state}) => {
          return (
            <Switch.Root
              checked={state.value}
              name={name}
              onCheckedChange={handleChange}
            >
              <Switch.HiddenInput />
              <Switch.Control />
              <Switch.Label>Subscribe to our Newsletter</Switch.Label>
            </Switch.Root>
          )
        }}
      </form.Field>

      <form.Field
        name="acceptTerms"
        validators={{
          onChange: (field) => (field.value ? undefined : errorMessage),
          onSubmit: (field) => (field.value ? undefined : errorMessage),
        }}
      >
        {({handleChange, name, state}) => {
          const fieldError =
            state.meta.errorMap["onChange"] || state.meta.errorMap["onSubmit"]
          return (
            <Switch.Root
              checked={state.value}
              invalid={!!fieldError}
              name={name}
              onCheckedChange={handleChange}
            >
              <Switch.HiddenInput />
              <Switch.Control />
              <Switch.Label>Accept Terms</Switch.Label>
              <Switch.ErrorText>{fieldError}</Switch.ErrorText>
            </Switch.Root>
          )
        }}
      </form.Field>

      <Button
        className="mt-1"
        emphasis="primary"
        size="sm"
        type="submit"
        variant="fill"
      >
        Submit
      </Button>
    </form>
  )
}

Tanstack form also supports ArkType.

Composite Guidelines

The composite elements are only intended to be used as direct descendants of the <Switch.Root> component.

/* Won't work alone ❌ */
<Switch.HiddenInput />
<Switch.Control />

/* Works as expected ✅ */
<Switch.Root>
  <Switch.HiddenInput />
  <Switch.Control />
</Switch.Root>

Shortcuts

Control and Thumb

The switch control automatically renders the thumb if no child elements are supplied. This:

<Switch.Control />

is equivalent to this:

<Switch.Control>
  <Switch.Thumb />
</Switch.Control>

API

<Switch.Root>

Groups all parts of the switch. Renders a <label> element by default.
PropTypeDefault
The controlled checked state of the switch
boolean
React children prop.
The initial checked state of the switch when rendered. Use when you don't need to control the checked state of the switch.
boolean
The document's text/writing direction.
'ltr' | 'rtl'
'ltr'
Whether the input is disabled. When true, prevents user interaction and applies visual styling to indicate the disabled state.
boolean
The id of the form that the switch belongs to.
string
A root node to correctly resolve document in custom environments. i.e., Iframes, Electron.
    () =>
    | Node
    | ShadowRoot
    | Document
    The ids of the elements that are associated with the switch. These will be automatically generated if omitted.
    Partial<{
    errorText: string
    hiddenInput: string
    label: string
    root: string
    }>
    Controls the visual error state of the input. When true, applies semantic error styling to indicate validation failure.
    boolean
    The name of the input field in a switch. Useful for form submission.
    string
    The callback invoked when the checked state changes.
      (
      checked: boolean,
      ) => void
      The callback invoked when the field is focused.
        (
        focused: boolean,
        ) => void
        Whether the input is read-only. When true, prevents user interaction while keeping the input focusable and visible.
        boolean
        Allows you to replace the component's HTML element with a different tag or component. Learn more
        | ReactElement
        | ((
        props: object,
        ) => ReactElement)
        Whether the input is required. When true, the input must have a value for form validation to pass.
        boolean
        Size of the component and its label.
        'sm' | 'md'
        "md"
        
        The value of switch input. Useful for form submission.
        string
        "on"
        
        Type
        boolean
        Description
        The controlled checked state of the switch
        Description
        React children prop.
        Type
        boolean
        Description
        The initial checked state of the switch when rendered. Use when you don't need to control the checked state of the switch.
        Type
        'ltr' | 'rtl'
        Description
        The document's text/writing direction.
        Type
        boolean
        Description
        Whether the input is disabled. When true, prevents user interaction and applies visual styling to indicate the disabled state.
        Type
        string
        Description
        The id of the form that the switch belongs to.
        Type
        () =>
        | Node
        | ShadowRoot
        | Document
        Description
        A root node to correctly resolve document in custom environments. i.e., Iframes, Electron.
          Type
          Partial<{
          errorText: string
          hiddenInput: string
          label: string
          root: string
          }>
          Description
          The ids of the elements that are associated with the switch. These will be automatically generated if omitted.
          Type
          boolean
          Description
          Controls the visual error state of the input. When true, applies semantic error styling to indicate validation failure.
          Type
          string
          Description
          The name of the input field in a switch. Useful for form submission.
          Type
          (
          checked: boolean,
          ) => void
          Description
          The callback invoked when the checked state changes.
            Type
            (
            focused: boolean,
            ) => void
            Description
            The callback invoked when the field is focused.
              Type
              boolean
              Description
              Whether the input is read-only. When true, prevents user interaction while keeping the input focusable and visible.
              Type
              | ReactElement
              | ((
              props: object,
              ) => ReactElement)
              Description
              Allows you to replace the component's HTML element with a different tag or component. Learn more
              Type
              boolean
              Description
              Whether the input is required. When true, the input must have a value for form validation to pass.
              Type
              'sm' | 'md'
              Description
              Size of the component and its label.
              Type
              string
              Description
              The value of switch input. Useful for form submission.

              <Switch>

              The Switch component supports all props from Switch.Root, plus the additional props listed below.

              PropType
              Props applied to the control element.
              {
              children?: ReactNode
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Optional error that describes the element when invalid is true.
              string
              Props applied to the error text element.
              {
              children?: ReactNode
              icon?:
              | LucideIcon
              | ReactNode
              id?: string
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Props applied to the hidden input element.
              any
              Optional label describing the element. Recommended. This element is automatically associated with the component's input element for accessibility.
              Props applied to the label element.
              {
              children?: ReactNode
              id?: string
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Props applied to the thumb element.
              {
              children?: ReactNode
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Type
              {
              children?: ReactNode
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Description
              Props applied to the control element.
              Type
              string
              Description
              Optional error that describes the element when invalid is true.
              Type
              {
              children?: ReactNode
              icon?:
              | LucideIcon
              | ReactNode
              id?: string
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Description
              Props applied to the error text element.
              Type
              any
              Description
              Props applied to the hidden input element.
              Description
              Optional label describing the element. Recommended. This element is automatically associated with the component's input element for accessibility.
              Type
              {
              children?: ReactNode
              id?: string
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Description
              Props applied to the label element.
              Type
              {
              children?: ReactNode
              render?:
              | Element
              | ((
              props: Props,
              ) => Element)
              }
              Description
              Props applied to the thumb element.