base-switch

PreviousNext
Docs
reuiui

Preview

Loading preview…
registry/default/ui/base-switch.tsx
import * as React from 'react';
import { Switch as SwitchPrimitive } from '@base-ui-components/react/switch';
import { cva, VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

// Define a context for `size` state
const SwitchContext = React.createContext<{ size: 'sm' | 'md' | 'lg' }>({
  size: 'md',
});

const useSwitchContext = () => {
  const context = React.useContext(SwitchContext);
  if (!context) {
    throw new Error('SwitchThumb must be used within a Switch component');
  }
  return context;
};

// Define the variants for the Switch using cva.
const switchVariants = cva(
  `
    peer data-[checked]:bg-primary data-[unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[unchecked]:bg-input/80 
    aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10 dark:aria-invalid:border-destructive dark:aria-invalid:ring-destructive/20
    inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] 
    disabled:cursor-not-allowed disabled:opacity-50
  `,
  {
    variants: {
      size: {
        sm: 'h-4 w-6',
        md: 'h-5 w-8',
        lg: 'h-6 w-10',
      },
    },
    defaultVariants: {
      size: 'md',
    },
  },
);

const switchThumbVariants = cva(
  'bg-background dark:data-[unchecked]:bg-foreground dark:data-[checked]:bg-primary-foreground pointer-events-none rounded-full ring-0 transition-transform flex items-center justify-center',
  {
    variants: {
      size: {
        sm: 'size-3 data-[checked]:translate-x-[calc(100%-2px)] data-[unchecked]:translate-x-0',
        md: 'size-4 data-[checked]:translate-x-[calc(100%-2px)] data-[unchecked]:translate-x-0',
        lg: 'size-5 data-[checked]:translate-x-[calc(100%-2px)] data-[unchecked]:translate-x-0',
      },
    },
    defaultVariants: {
      size: 'md',
    },
  },
);

function Switch({
  className,
  children,
  size = 'md',
  ...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & VariantProps<typeof switchVariants>) {
  const effectiveSize = size ?? 'md';
  return (
    <SwitchContext.Provider value={{ size: effectiveSize }}>
      <SwitchPrimitive.Root
        data-slot="switch"
        className={cn(switchVariants({ size: effectiveSize }), className)}
        {...props}
      >
        {children ?? <SwitchThumb />}
      </SwitchPrimitive.Root>
    </SwitchContext.Provider>
  );
}

function SwitchThumb({
  className,
  size,
  ...props
}: React.ComponentProps<typeof SwitchPrimitive.Thumb> & Partial<VariantProps<typeof switchThumbVariants>>) {
  const context = useSwitchContext();
  const effectiveSize = size ?? context.size;

  return (
    <SwitchPrimitive.Thumb
      data-slot="switch-thumb"
      className={cn(switchThumbVariants({ size: effectiveSize }), className)}
      {...props}
    />
  );
}

export { Switch, SwitchThumb, switchVariants };

Installation

npx shadcn@latest add @reui/base-switch

Usage

import { BaseSwitch } from "@/components/ui/base-switch"
<BaseSwitch />