# Rocker Switch

> A rocker switch, as on a mixer or a synth, the side pressed down sunk into the panel.

```tsx
"use client";

import { useState } from "react";
import { Led } from "@/components/audio-ui/led";
import { RockerSwitch } from "@/components/audio-ui/rocker-switch";

export function RockerSwitchDemo() {
  const [power, setPower] = useState(true);
  return (
    <div className="flex items-center gap-8">
      <div className="flex flex-col items-center gap-2">
        <Led label="Power" on={power} />
        <RockerSwitch
          aria-label="Power"
          checked={power}
          onCheckedChange={setPower}
        />
        <span className="text-muted-foreground text-xs uppercase tracking-wide">
          Power
        </span>
      </div>
      <RockerSwitch
        aria-label="Phase invert"
        defaultChecked={false}
        offLabel="Ø"
        onLabel="Ø"
        orientation="horizontal"
        size="sm"
      />
      <RockerSwitch aria-label="Pad, disabled" disabled size="sm" />
    </div>
  );
}
```

A rocker, as on a mixer's or a synth's panel: two sides, the one pressed down sunk into the panel. Underneath it is a switch, with its role, Space to flip it, and its form value (`name`). Its shape follows the style, from a pill to a square.

## Installation

**CLI**

```bash
npx shadcn@latest add @audio-ui/rocker-switch
```

**Manual**

**1. Install the following dependencies:**

```bash
npm install @base-ui/react class-variance-authority
```

**2. Copy and paste the following code into your project.**

```tsx title="components/audio-ui/rocker-switch.tsx"
"use client";

import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
import { cva, type VariantProps } from "class-variance-authority";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";

const rockerSwitchVariants = cva(
  "rounded-lg bg-muted p-0.5 outline-none ring-1 ring-border transition-shadow focus-visible:ring-2 focus-visible:ring-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 group/rocker inline-flex shrink-0 gap-0.5",
  {
    compoundVariants: [
      {
        className: "h-14 w-8 text-[0.6875rem]",
        orientation: "vertical",
        size: "default",
      },
      {
        className: "h-10 w-6 text-[0.5625rem]",
        orientation: "vertical",
        size: "sm",
      },
      {
        className: "h-8 w-14 text-[0.6875rem]",
        orientation: "horizontal",
        size: "default",
      },
      {
        className: "h-6 w-10 text-[0.5625rem]",
        orientation: "horizontal",
        size: "sm",
      },
    ],
    defaultVariants: { orientation: "vertical", size: "default" },
    variants: {
      /** Stood up, on at the top, or laid down, on at the right. */
      orientation: {
        horizontal: "flex-row-reverse",
        vertical: "flex-col",
      },
      size: {
        default: "",
        sm: "",
      },
    },
  }
);

export interface RockerSwitchProps
  extends Omit<SwitchPrimitive.Root.Props, "children" | "className">,
    VariantProps<typeof rockerSwitchVariants> {
  className?: string;
  /** What the off side reads. @default "O" */
  offLabel?: ReactNode;
  /** What the on side reads. @default "I" */
  onLabel?: ReactNode;
}

/**
 * A rocker, as on a mixer's or a synth's panel: two sides, the one pressed
 * down sunk into the panel. A switch underneath, with its role, its keyboard
 * (Space) and its form value; name it with `aria-label`.
 */
export function RockerSwitch({
  className,
  offLabel = "O",
  onLabel = "I",
  orientation,
  size,
  ...props
}: RockerSwitchProps) {
  return (
    <SwitchPrimitive.Root
      className={cn(rockerSwitchVariants({ orientation, size }), className)}
      data-orientation={orientation ?? "vertical"}
      data-size={size ?? "default"}
      data-slot="rocker-switch"
      {...props}
    >
      <span
        aria-hidden="true"
        className="rounded-md font-mono font-semibold transition-[background-color,box-shadow,color] duration-100 bg-background text-foreground shadow-sm group-data-checked/rocker:bg-transparent group-data-checked/rocker:text-primary group-data-checked/rocker:shadow-inner flex flex-1 items-center justify-center"
        data-slot="rocker-switch-on"
      >
        {onLabel}
      </span>
      <span
        aria-hidden="true"
        className="rounded-md font-mono font-semibold transition-[background-color,box-shadow,color] duration-100 text-muted-foreground shadow-inner group-data-checked/rocker:bg-background group-data-checked/rocker:text-foreground group-data-checked/rocker:shadow-sm flex flex-1 items-center justify-center"
        data-slot="rocker-switch-off"
      >
        {offLabel}
      </span>
    </SwitchPrimitive.Root>
  );
}
```

**3. Update the import paths to match your project setup.**

**Agent**

Give your agent the skill, and it picks the component, installs it and wires it to your audio:

```bash
npx skills add ouestlabs/audio-ui
```

Then ask it:

```text
Add the rocker switch from audio/ui to my project and wire it to my audio code.
```

To let the agent read the docs live, connect the [MCP server](/docs/mcp). More in [Agent Skill](/docs/agents).

## Usage

```tsx
import { RockerSwitch } from "@/components/audio-ui/rocker-switch";

<RockerSwitch aria-label="Power" checked={power} onCheckedChange={setPower} />
<RockerSwitch aria-label="Phase invert" offLabel="Ø" onLabel="Ø" orientation="horizontal" size="sm" />
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked, onCheckedChange` | `boolean, (checked) => void` |  | Whether it is on, when it is held outside. |
| `defaultChecked` | `boolean` | `false` | Whether it starts on, when it keeps its own state. |
| `onLabel, offLabel` | `ReactNode` | `"I", "O"` | What the on and the off side read. |
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Stood up, on at the top, or laid down, on at the right. |
| `size` | `"default" \| "sm"` | `"default"` | Its size. |
| `disabled` | `boolean` | `false` | Takes no input. |
