VibeUI
ComponentsDocs
VibeUI

Components with taste, that you own. Copy the source, retint the tokens, keep every line.

Library

  • All components
  • Signature pieces
  • Text effects
  • Footers

Docs

  • Introduction
  • Installation
  • Theming
  • CLI

Project

  • GitHub
  • Agents and MCP
  • Issues
  • MIT license
© 2026 VibeUI · MIT licensed
  1. Components
  2. Forms
  3. Switch

Switch

On/off switch with an accent track and sprung thumb.

FormsClient component

Email notifications

Weekly product digest.

Reduced motion

Tone animations down.

Disabled

Install

bash
npx @agents-npm-packages/vibeui add switch --dir ./src/components/ui

Import

tsx
import { Switch } from "@/components/ui/switch"

API

Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.

<Switch>

PropTypeDefaultDescription
checkedboolean—Controlled checked state.
defaultCheckedboolean—Initial checked state when uncontrolled.
onCheckedChange((checked: boolean) => void)—Called with the new checked state.
disabledboolean—Prevents interaction and dims the control.
classNamestring—Extra classes, merged with the component's own.
idstring—DOM id.
aria-labelstring—Accessible name, required when there is no visible label.

Source

src/components/ui/switch.tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

/** On/off switch with an accent track and sprung thumb. */
export function Switch({
  checked,
  defaultChecked,
  onCheckedChange,
  disabled,
  className,
  id,
  "aria-label": ariaLabel,
}: {
  checked?: boolean;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
  disabled?: boolean;
  className?: string;
  id?: string;
  "aria-label"?: string;
}) {
  const [uncontrolled, setUncontrolled] = React.useState(!!defaultChecked);
  const isOn = checked ?? uncontrolled;

  return (
    <button
      type="button"
      role="switch"
      id={id}
      aria-checked={isOn}
      aria-label={ariaLabel}
      disabled={disabled}
      onClick={() => {
        const next = !isOn;
        setUncontrolled(next);
        onCheckedChange?.(next);
      }}
      className={cn(
        "group relative inline-flex h-6 w-[42px] shrink-0 items-center rounded-full border p-0.5 transition-colors duration-200 ease-out disabled:pointer-events-none disabled:opacity-45",
        isOn
          ? "border-transparent bg-accent"
          : "border-line-strong bg-surface-sunken hover:border-faint",
        className,
      )}
    >
      <span
        className={cn(
          "pointer-events-none block h-5 w-5 rounded-full shadow-[0_1px_3px_rgb(0_0_0/0.3)] transition-[transform,width,background-color] duration-300 ease-spring group-active:w-[22px]",
          isOn ? "bg-accent-ink" : "bg-muted",
          isOn ? "translate-x-[18px] group-active:translate-x-4" : "translate-x-0",
        )}
      />
    </button>
  );
}

Exports Switch. Peer dependencies: clsx, tailwind-merge.

PreviousSliderNextTag Input