Form controls

Switch
v1.1.2
Documentation Under Review

A toggle control that allows users to switch between on and off states.

Installation

Install the switch component via your package manager:

npm install @pmi/catalyst-switch

Usage

Import the switch components:

import { SwitchRoot, SwitchThumb } from '@pmi/catalyst-switch';

Examples

Basic Switch

A simple toggle switch with checked and unchecked states.

Preview

Switch Sizes

Switch supports three size variants: small, medium, and large.

Preview

Disabled State

Switches can be disabled to prevent user interaction.

Preview

With Label

Integrate switch with labels for better accessibility and user experience.

Preview

With Label Sizes

All switch sizes work seamlessly with corresponding label sizes.

Preview

Layout Variant

Switch and label can be arranged in different layouts, including reversed order.

Preview

With Label and SubText

Complete form control with label and helper text.

Preview
Receive product updates and promotional offers

Custom Thumb

Use the asChild prop to customize the switch thumb with icons or other elements.

Preview

Custom Styled (Infinity Design)

Custom switch styling with the Infinity design system's aqua color palette, including size-specific variants.

Preview

Themes

Preview
Preview

Component API

SwitchRoot

The root container for the switch component.

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Size variant of the switch
checkedboolean-Controlled checked state
defaultCheckedboolean-Uncontrolled default checked state
disabledbooleanfalseWhether the switch is disabled
requiredbooleanfalseWhether the switch is required in a form
namestring-Name attribute for form submission
valuestring'on'Value attribute for form submission
onCheckedChange(checked: boolean) => void-Callback when checked state changes
classNamestring-Additional CSS classes
idstring-HTML id attribute for label association
unstyledbooleanfalseDisables all default CVA/Tailwind styling, only applies className.

SwitchThumb

The thumb element that moves when the switch is toggled.

PropTypeDefaultDescription
asChildbooleanfalseUse Slot to render custom thumb element
classNamestring-Additional CSS classes
unstyledbooleanfalseDisables all default CVA/Tailwind styling, only applies className.

Size Variants

Custom Styling

Infinity Design System

The Infinity design uses aqua colors for switches with size-specific thumb translations. Here are the complete styling patterns using CVA (class-variance-authority):

SwitchRoot Styles with CVA

import { cva } from 'class-variance-authority';

const switchRootInfinityVariants = cva(
  [
    // Base styles
    'border border-solid outline-pmi-aqua-500',

    // Checked state
    'data-[state=checked]:border-pmi-aqua-500',
    'data-[state=checked]:bg-pmi-aqua-500',
    'data-[state=checked]:hover:bg-pmi-aqua-800',
    'data-[state=checked]:hover:border-pmi-aqua-800',

    // Unchecked state
    'data-[state=unchecked]:border-neutral-500',
    'data-[state=unchecked]:bg-white',
    'data-[state=unchecked]:hover:bg-pmi-aqua-100',

    // Dark mode - checked
    'dark:data-[state=checked]:bg-pmi-aqua-500',
    'dark:data-[state=checked]:hover:bg-pmi-aqua-800',
    'dark:data-[state=checked]:border-pmi-aqua-500',
    'dark:data-[state=checked]:hover:border-pmi-aqua-800',

    // Dark mode - unchecked
    'dark:data-[state=unchecked]:border-neutral-500',
    'dark:data-[state=unchecked]:bg-neutral-800',
    'dark:data-[state=unchecked]:hover:bg-pmi-aqua-800',
    'dark:outline-pmi-aqua-500',
  ],
  {
    variants: {
      size: {
        // Small and large need custom translate values
        sm: ['[&_span]:data-[state=checked]:translate-x-[15px]'],
        md: undefined, // Uses default from component
        lg: ['[&_span]:data-[state=checked]:translate-x-[15px]'],
      },
    },
    defaultVariants: {
      size: 'md',
    },
  }
);

// Usage
<SwitchRoot
  size="sm"
  className={switchRootInfinityVariants({ size: 'sm' })}
>
  <SwitchThumb className={switchThumbInfinityStyles} />
</SwitchRoot>

Important: Size-specific variants are needed because sm and lg switches require custom translate-x values (15px) for proper thumb positioning, while md uses the component's default translation (20px).

SwitchThumb Styles

// Thumb colors for Infinity design
const switchThumbInfinityStyles = cn(
  'bg-pmi-aqua-500',
  'data-[state=checked]:bg-white',
  'dark:bg-white',
  'dark:data-[state=checked]:bg-white'
);

Accessibility

Keyboard Navigation

  • Space - Toggle the switch state
  • Enter - Toggle the switch state
  • Tab - Move focus to or from the switch

ARIA Attributes

The Switch component automatically includes proper ARIA attributes:

  • role="switch" - Identifies the element as a switch
  • aria-checked - Indicates the current checked state
  • aria-disabled - Indicates if the switch is disabled
  • Associated label via id and htmlFor attributes

Screen Reader Support

  • State changes are announced (on/off)
  • Labels are properly associated with switches
  • Disabled state is announced

Best Practices

  1. Always provide a label - Use the Label component or aria-label for context
  2. Use clear labels - Describe what the switch controls
  3. Provide helper text - Add SubText for additional context when needed
  4. Indicate disabled state - Make it clear when a switch cannot be toggled
  5. Use appropriate sizing - Smaller sizes for product interfaces, larger for touch

Integration Patterns

Controlled State

import { SwitchRoot, SwitchThumb } from '@pmi/catalyst-switch';
import { useState } from 'react';

export default function ControlledSwitch() {
  const [checked, setChecked] = useState(false);

  return (
    <SwitchRoot checked={checked} onCheckedChange={setChecked}>
      <SwitchThumb />
    </SwitchRoot>
  );
}

Form Integration

<form onSubmit={handleSubmit}>
  <SwitchRoot name="notifications" value="enabled">
    <SwitchThumb />
  </SwitchRoot>
</form>

With React Hook Form

import { useForm, Controller } from 'react-hook-form';

function MyForm() {
  const { control } = useForm();

  return (
    <Controller
      name="notifications"
      control={control}
      render={({ field }) => (
        <SwitchRoot
          checked={field.value}
          onCheckedChange={field.onChange}
        >
          <SwitchThumb />
        </SwitchRoot>
      )}
    />
  );
}
  • Checkbox - For multiple selections
  • Radio - For single selection from options
  • Label - For form labels
  • SubText - For helper text

On this page