# Hardware Switch

> Rocker, safety-cover flip and key switches you can almost feel.

Category: Instruments. Docs: https://neonkit.heyaadi.com/components/hardware-switch

## Installation

```bash
npx shadcn@latest add https://neonkit.heyaadi.com/r/hardware-switch.json
```

With the `@neonkit` namespace configured: `npx shadcn@latest add @neonkit/hardware-switch`.

npm dependencies: none. Also needs `lib/utils.ts` (`cn`) and `lib/neon.ts` (`neonVars`), which the CLI adds.

## Usage

```tsx
import { HardwareSwitch } from "@/components/ui/hardware-switch"

<HardwareSwitch variant="flip" label="Arm" />
```

## Examples

### Cockpit toggle bank

Guarded toggles feed a DC bus. Switch on the fuel pump and the key unlocks; turn it to start the engine.

```tsx
"use client";

import * as React from "react";
import { HardwareSwitch } from "@/components/ui/hardware-switch";
import { cn } from "@/lib/utils";

const breakers = [
  { id: "battery", label: "Batt" },
  { id: "gen1", label: "Gen 1" },
  { id: "gen2", label: "Gen 2" },
  { id: "fuel", label: "Fuel pump" },
] as const;

type Breaker = (typeof breakers)[number]["id"];

function Fastener({ className }: { className?: string }) {
  return (
    <span
      aria-hidden
      className={cn(
        "absolute size-2.5 rounded-full bg-[radial-gradient(circle_at_35%_30%,#4a4f5b,#121419)] shadow-[0_1px_1px_rgb(0_0_0/0.8),0_0_0_1px_rgb(0_0_0/0.5)]",
        "after:absolute after:left-1/2 after:top-1/2 after:h-px after:w-2 after:-translate-x-1/2 after:-translate-y-1/2 after:bg-black/80",
        className,
      )}
    />
  );
}

export default function HardwareSwitchDemo() {
  const [on, setOn] = React.useState<Record<Breaker, boolean>>({
    battery: true,
    gen1: true,
    gen2: false,
    fuel: false,
  });
  const [engine, setEngine] = React.useState(false);

  const generators = Number(on.gen1) + Number(on.gen2);
  const volts = !on.battery ? 0 : engine && generators > 0 ? 28.2 : 24.6;
  const canStart = on.battery && on.fuel;
  const running = engine && canStart;

  return (
    <div className="relative w-full max-w-[600px] rounded-xl border border-white/[0.07] bg-[linear-gradient(180deg,#1a1d24,#101217)] px-6 pb-6 pt-5 shadow-[inset_0_1px_0_rgb(255_255_255/0.07),0_30px_60px_-30px_rgb(0_0_0/0.9)] sm:px-8">
      <Fastener className="left-2.5 top-2.5" />
      <Fastener className="right-2.5 top-2.5" />
      <Fastener className="bottom-2.5 left-2.5" />
      <Fastener className="bottom-2.5 right-2.5" />

      <div className="flex items-center gap-3 text-[10px] font-semibold uppercase tracking-[0.3em] text-white/60">
        <span className="h-px flex-1 bg-white/15" />
        Electrical
        <span className="h-px flex-1 bg-white/15" />
      </div>

      <div className="mt-6 flex flex-wrap items-end justify-center gap-x-7 gap-y-6">
        {breakers.map((b) => (
          <HardwareSwitch
            key={b.id}
            variant="flip"
            label={b.label}
            checked={on[b.id]}
            onCheckedChange={(checked) => {
              setOn((current) => ({ ...current, [b.id]: checked }));
              if (!checked && (b.id === "battery" || b.id === "fuel")) setEngine(false);
            }}
          />
        ))}
        <span aria-hidden className="hidden h-24 w-px self-center bg-white/[0.08] sm:block" />
        <HardwareSwitch
          variant="key"
          label="Start"
          checked={running}
          disabled={!canStart}
          onCheckedChange={setEngine}
        />
      </div>

      <div className="mt-6 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-black/60 bg-[#07080b] px-4 py-2.5 font-mono text-xs shadow-[inset_0_1px_4px_rgb(0_0_0/0.9),0_1px_0_rgb(255_255_255/0.05)]">
        <span className="text-white/50">
          DC bus{" "}
          <span
            className={cn(
              "tabular-nums transition-colors duration-200",
              volts > 0
                ? "[color:color-mix(in_srgb,rgb(var(--neon-accent,34_211_238))_35%,white)] [text-shadow:0_0_8px_rgb(var(--neon-accent,34_211_238)/0.7)]"
                : "text-white/25",
            )}
          >
            {volts.toFixed(1)} V
          </span>
        </span>
        <span className={cn("transition-colors duration-200", running ? "text-white/85" : "text-white/50")}>
          {running ? "Engine running" : canStart ? "Ready to start" : on.battery ? "Fuel pump off" : "Battery off"}
        </span>
      </div>
    </div>
  );
}
```

### Variants

rocker lights its I. flip lifts a hinged guard, then throws the lever; turning off reverses the order. key turns a quarter turn and lights the ring around the lock.

```tsx
"use client";

import { HardwareSwitch } from "@/components/ui/hardware-switch";

const variants = [
  { variant: "rocker", label: "Mains", note: "Lit I/O rocker" },
  { variant: "flip", label: "Arm", note: "Guard opens, then the toggle" },
  { variant: "key", label: "Deploy", note: "Turns 90° into a detent" },
] as const;

export default function HardwareSwitchVariants() {
  return (
    <div className="flex flex-wrap items-end justify-center gap-x-14 gap-y-10">
      {variants.map((v, i) => (
        <div key={v.variant} className="flex flex-col items-center gap-3">
          <HardwareSwitch variant={v.variant} label={v.label} defaultChecked={i === 0} />
          <span className="text-[13px] text-white/55">{v.note}</span>
        </div>
      ))}
    </div>
  );
}
```

### In a form

With a name, each switch submits value ("on" by default) only while it is on, like a checkbox.

```tsx
"use client";

import * as React from "react";
import { Button } from "@/components/ui/button";
import { HardwareSwitch } from "@/components/ui/hardware-switch";

export default function HardwareSwitchForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null);

  return (
    <form
      className="w-full max-w-md rounded-xl border border-white/10 bg-white/[0.03] p-6"
      onSubmit={(event) => {
        event.preventDefault();
        const on = [...new FormData(event.currentTarget).keys()];
        setSubmitted(on.length ? `Enabled: ${on.join(", ")}` : "Everything is off");
      }}
    >
      <h3 className="text-sm font-medium text-white/90">Release controls</h3>
      <p className="mt-1 text-xs text-white/55">Only switches that are on are submitted, like checkboxes.</p>
      <div className="mt-6 flex items-end justify-around gap-6">
        <HardwareSwitch variant="rocker" name="read-only" label="Read-only" />
        <HardwareSwitch variant="flip" name="migrations" label="Migrations" defaultChecked />
        <HardwareSwitch variant="key" name="deploys" label="Deploys" />
      </div>
      <div className="mt-6 flex items-center justify-between gap-4 border-t border-white/[0.06] pt-4">
        <p aria-live="polite" className="min-w-0 truncate text-xs text-white/55">
          {submitted ?? "Nothing submitted yet"}
        </p>
        <Button type="submit" size="sm">
          Apply
        </Button>
      </div>
    </form>
  );
}
```

### Sizes, colors and disabled

The hardware is drawn in em, so size scales every part together.

```tsx
"use client";

import { HardwareSwitch } from "@/components/ui/hardware-switch";

export default function HardwareSwitchSizes() {
  return (
    <div className="flex flex-wrap items-end justify-center gap-10">
      <HardwareSwitch size="sm" label="Aux" color="amber" defaultChecked />
      <HardwareSwitch size="md" label="Heater" color="red" defaultChecked />
      <HardwareSwitch size="lg" label="Pump" color="green" defaultChecked />
      <HardwareSwitch size="md" variant="key" label="Service" color="violet" defaultChecked />
      <HardwareSwitch size="md" label="Locked out" disabled />
    </div>
  );
}
```

## API reference

### HardwareSwitch

className and style go on the outer element. The ref and every other button attribute go on the button, which has role="switch".

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"rocker" \| "flip" \| "key"` | `"rocker"` | A lit I/O rocker, a toggle under a safety guard, or a key switch. |
| `checked` | `boolean` | — | Controlled state. |
| `defaultChecked` | `boolean` | `false` | Initial state when uncontrolled. |
| `onCheckedChange` | `(checked: boolean) => void` | — | Called with the new state on every flip. |
| `label` | `ReactNode` | — | Engraved legend under the switch. Names it, and clicking it flips the switch. |
| `name` | `string` | — | Submits value under this name while on, through a hidden checkbox. |
| `value` | `string` | `"on"` | Value submitted while on. |
| `color` | `NeonColor` | `inherited` | Palette name or hex for the lamp, guard tint and ring. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Scales the hardware: 12, 16 or 20px to the em. |
| `disabled` | `boolean` | `false` | Locks the switch and keeps its lamp dark. |

## Accessibility

- Renders a native button with role="switch" and aria-checked, so Space and Enter flip it.
- A visible label names the switch through aria-labelledby; without one, pass aria-label.
- The guard and lever, the key and the rocker all move with transforms only, and stand still under reduced motion.

## Source

`components/ui/hardware-switch.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { neonVars, type NeonColor } from "@/lib/neon";

// The hardware is drawn in em, so the font size on the button scales all of it.
const sizes = {
  sm: { device: "text-[12px]", label: "text-[12px]" },
  md: { device: "text-[16px]", label: "text-[12.5px]" },
  lg: { device: "text-[20px]", label: "text-[13px]" },
} as const;

// A struck lamp: two quick drops in brightness, then it holds.
const keyframes = `@keyframes nk-hardware-switch-ignite{0%{opacity:0}12%{opacity:.85}22%{opacity:.15}40%{opacity:1}52%{opacity:.4}68%,100%{opacity:1}}`;
const ignite = "[animation:nk-hardware-switch-ignite_360ms_linear_both] motion-reduce:[animation:none]";

const CORE = "color-mix(in srgb, rgb(var(--neon)) 30%, white)";
const glow = "shadow-[0_0_0.2em_rgb(var(--neon)/var(--neon-glow,1)),0_0_0.6em_rgb(var(--neon)/CALC(0.75*var(--neon-glow,1))),0_0_1.3em_rgb(var(--neon)/CALC(0.35*var(--neon-glow,1)))]";
const glass = "bg-[rgb(var(--neon)/0.15)] shadow-[inset_0_1px_1px_rgb(0_0_0/0.6),0_1px_0_rgb(255_255_255/0.06)]";

type VariantProps = { checked: boolean; lit: boolean; ignitions: number };

/** A lamp: dark glass that lights with a white-hot core and a colored halo. */
function Lamp({ on, ignitions, delay = 0, className }: { on: boolean; ignitions: number; delay?: number; className?: string }) {
  return (
    <span aria-hidden className={cn("absolute", glass, className)}>
      <span
        key={ignitions}
        className={cn(
          "absolute inset-0 rounded-[inherit] transition-opacity duration-150",
          glow,
          on ? "opacity-100" : "opacity-0",
          on && ignitions > 0 && ignite,
        )}
        style={{ backgroundColor: CORE, animationDelay: on && delay ? `${delay}ms` : undefined }}
      />
    </span>
  );
}

function Rocker({ checked, lit, ignitions }: VariantProps) {
  return (
    <span
      aria-hidden
      className="relative block h-[4.6em] w-[2.9em] rounded-[0.45em] border border-black/80 bg-[linear-gradient(180deg,#252830,#0d0e12)] shadow-[0_0.4em_0.7em_-0.2em_rgb(0_0_0/0.85),inset_0_1px_0_rgb(255_255_255/0.1),inset_0_-1px_0_rgb(0_0_0/0.6)]"
    >
      {/* The well the paddle sits in, and the light the lamp throws into it */}
      <span className="absolute inset-[0.22em] rounded-[0.3em] bg-[#030405] shadow-[inset_0_0.1em_0.25em_rgb(0_0_0/0.95)]" />
      <span
        className={cn(
          "absolute inset-[0.1em] rounded-[0.4em] bg-[radial-gradient(ellipse_at_50%_30%,rgb(var(--neon)/CALC(0.35*var(--neon-glow,1))),transparent_65%)] transition-opacity duration-200",
          checked && lit ? "opacity-100" : "opacity-0",
        )}
      />
      <span className="absolute inset-[0.3em] [perspective:12em]">
        <span
          className={cn(
            "absolute inset-0 rounded-[0.25em] bg-[#1c1f26] shadow-[inset_0_0_0_1px_rgb(255_255_255/0.05)]",
            "transition-transform duration-150 ease-[cubic-bezier(0.3,1.5,0.6,1)] motion-reduce:transition-none",
            checked ? "[transform:rotateX(16deg)]" : "[transform:rotateX(-16deg)]",
          )}
        >
          {/* Shading for each position: the pressed half falls into shadow */}
          <span
            className={cn(
              "absolute inset-0 rounded-[inherit] bg-[linear-gradient(180deg,rgb(0_0_0/0.7),rgb(0_0_0/0.38)_46%,rgb(255_255_255/0.2)_50%,rgb(255_255_255/0.09)_68%,rgb(0_0_0/0.1))] transition-opacity duration-150",
              checked ? "opacity-100" : "opacity-0",
            )}
          />
          <span
            className={cn(
              "absolute inset-0 rounded-[inherit] bg-[linear-gradient(180deg,rgb(0_0_0/0.1),rgb(255_255_255/0.09)_32%,rgb(255_255_255/0.2)_50%,rgb(0_0_0/0.38)_54%,rgb(0_0_0/0.7))] transition-opacity duration-150",
              checked ? "opacity-0" : "opacity-100",
            )}
          />
          <span className="absolute inset-x-[0.2em] top-1/2 h-px bg-black/50" />
          {/* "I" is a light pipe, "O" is printed */}
          <Lamp on={checked && lit} ignitions={ignitions} className="left-1/2 top-[0.55em] h-[0.95em] w-[0.16em] -translate-x-1/2 rounded-full" />
          <span className="absolute bottom-[0.6em] left-1/2 size-[0.72em] -translate-x-1/2 rounded-full border-[0.11em] border-white/40" />
        </span>
      </span>
    </span>
  );
}

function Flip({ checked, lit, ignitions }: VariantProps) {
  return (
    <span
      aria-hidden
      className="relative block h-[5em] w-[3em] rounded-[0.45em] border border-black/80 bg-[linear-gradient(180deg,#2a2d35,#121318)] shadow-[0_0.4em_0.7em_-0.2em_rgb(0_0_0/0.85),inset_0_1px_0_rgb(255_255_255/0.1),inset_0_-1px_0_rgb(0_0_0/0.6)]"
    >
      {/* Bushing and nut */}
      <span className="absolute left-1/2 top-[2.55em] size-[1.3em] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[conic-gradient(from_30deg,#7c828e,#2a2d35_17%,#9399a5_33%,#30333b_50%,#878d99_67%,#272a31_83%,#7c828e)] shadow-[0_0.1em_0.2em_rgb(0_0_0/0.8)]" />
      <span className="absolute left-1/2 top-[2.55em] size-[0.72em] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[radial-gradient(circle_at_40%_35%,#3d414b,#0b0c0f)] shadow-[inset_0_0.05em_0.1em_rgb(0_0_0/0.9)]" />

      {/* The bat-handle lever swings through the panel's normal, toward you */}
      <span className="absolute left-1/2 top-[2.55em] size-0 [perspective:9em]">
        <span
          className={cn(
            "absolute bottom-0 left-[-0.19em] h-[1.75em] w-[0.38em] origin-bottom rounded-full [transform-style:preserve-3d]",
            "bg-[linear-gradient(90deg,#30343d,#dfe3ea_38%,#8d939f_58%,#262930)]",
            "transition-transform duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none",
            checked ? "delay-[170ms] [transform:rotateX(-52deg)]" : "delay-[0ms] [transform:rotateX(-128deg)]",
          )}
        >
          <span
            className={cn(
              "absolute -top-[0.2em] left-[-0.12em] size-[0.62em] rounded-full bg-[radial-gradient(circle_at_38%_30%,#ffffff,#c9ced8_22%,#5d6371_62%,#1d2026)]",
              "transition-transform duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none",
              checked ? "delay-[170ms] [transform:rotateX(52deg)]" : "delay-[0ms] [transform:rotateX(128deg)]",
            )}
          />
        </span>
      </span>

      <Lamp
        on={checked && lit}
        ignitions={ignitions}
        delay={300}
        className="bottom-[0.4em] left-1/2 size-[0.4em] -translate-x-1/2 rounded-full"
      />

      {/* Hinge barrel */}
      <span className="absolute inset-x-[0.3em] top-[0.32em] h-[0.42em] rounded-full bg-[linear-gradient(180deg,#6b717d,#2a2d35_55%,#15171b)] shadow-[0_0.08em_0.12em_rgb(0_0_0/0.7)]" />

      {/* The guard: tinted glass, hinged on the barrel. It lifts first, then the lever flips. */}
      <span
        className={cn(
          "absolute inset-x-[0.18em] top-[0.53em] h-[4.25em] origin-top [transform-style:preserve-3d] [transform:perspective(18em)_rotateX(var(--nk-cover))]",
          "transition-transform motion-reduce:transition-none",
          checked
            ? "duration-[260ms] ease-[cubic-bezier(0.3,1.25,0.6,1)] [--nk-cover:106deg]"
            : "delay-[120ms] duration-[220ms] ease-[cubic-bezier(0.5,0,0.3,1)] [--nk-cover:0deg] group-hover/hw:[--nk-cover:12deg] group-active/hw:[--nk-cover:24deg]",
        )}
      >
        <span className="absolute inset-0 rounded-b-[0.4em] rounded-t-[0.2em] border border-[rgb(var(--neon)/0.35)] bg-[linear-gradient(180deg,rgb(var(--neon)/0.2),rgb(var(--neon)/0.09)_60%,rgb(var(--neon)/0.16))] shadow-[inset_0_0_0.6em_rgb(var(--neon)/CALC(0.1*var(--neon-glow,1))),inset_0.12em_0_0_rgb(255_255_255/0.1),0_0.3em_0.5em_-0.1em_rgb(0_0_0/0.6)] [backface-visibility:hidden]">
          <span className="absolute inset-y-[0.3em] left-[0.25em] w-[0.12em] rounded-full bg-white/20" />
          <span className="absolute inset-x-[0.2em] bottom-[0.18em] h-[0.4em] rounded-[0.2em] bg-[rgb(var(--neon)/0.16)] shadow-[inset_0_1px_0_rgb(255_255_255/0.16)]" />
        </span>
        <span className="absolute inset-0 rounded-b-[0.4em] rounded-t-[0.2em] border border-[rgb(var(--neon)/0.3)] bg-[linear-gradient(180deg,rgb(var(--neon)/0.1),rgb(var(--neon)/0.22))] [backface-visibility:hidden] [transform:rotateX(180deg)]" />
      </span>
    </span>
  );
}

function Key({ checked, lit, ignitions }: VariantProps) {
  return (
    <span aria-hidden className="relative block size-[4.8em]">
      {/* Position marks: 0 at twelve o'clock, I at three */}
      <span className="absolute left-1/2 top-[0.05em] size-[0.5em] -translate-x-1/2 rounded-full border-[0.09em] border-white/40" />
      <Lamp on={checked && lit} ignitions={ignitions} delay={180} className="right-[0.12em] top-1/2 h-[0.14em] w-[0.55em] -translate-y-1/2 rounded-full" />

      {/* Lit ring around the lock */}
      <Lamp on={checked && lit} ignitions={ignitions} delay={180} className="inset-[0.72em] rounded-full" />
      <span className="absolute inset-[0.84em] rounded-full bg-[conic-gradient(from_20deg,#8d939f,#2a2d35_22%,#a9afba_48%,#2b2e36_72%,#8d939f)] shadow-[0_0.15em_0.3em_rgb(0_0_0/0.85)]" />
      <span className="absolute inset-[1.08em] rounded-full bg-[radial-gradient(circle_at_42%_36%,#3a3e48,#101216_75%)] shadow-[inset_0_0.08em_0.15em_rgb(0_0_0/0.9),0_0_0_1px_rgb(0_0_0/0.5)]" />

      {/* The key, turning 90° into a detent */}
      <span className="absolute inset-0 [filter:drop-shadow(0_0.18em_0.2em_rgb(0_0_0/0.75))]">
        <span
          className={cn(
            "absolute inset-0 transition-transform duration-300 ease-[cubic-bezier(0.3,1.45,0.5,1)] motion-reduce:transition-none",
            checked ? "rotate-90 group-hover/hw:rotate-[84deg]" : "rotate-0 group-hover/hw:rotate-[6deg]",
          )}
        >
          <span className="absolute left-1/2 top-1/2 h-[2.6em] w-[0.86em] -translate-x-1/2 -translate-y-1/2 rounded-[0.32em] border border-black/70 bg-[linear-gradient(90deg,#15171c,#41464f_38%,#2a2d34_62%,#0f1114)] shadow-[inset_0_1px_0_rgb(255_255_255/0.18)]">
            <span className="absolute inset-x-[0.2em] top-[0.22em] h-[0.5em] rounded-[0.18em] bg-[linear-gradient(90deg,rgb(255_255_255/0.08),rgb(255_255_255/0.22),rgb(255_255_255/0.05))]" />
            <span className="absolute bottom-[0.3em] left-1/2 size-[0.28em] -translate-x-1/2 rounded-full bg-black/80 shadow-[0_1px_0_rgb(255_255_255/0.1)]" />
          </span>
        </span>
      </span>
    </span>
  );
}

const variants = { rocker: Rocker, flip: Flip, key: Key } as const;

export interface HardwareSwitchProps
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color" | "onChange" | "value" | "defaultChecked"> {
  /** Palette name or any hex color. Inherits `--neon-accent` when omitted. */
  color?: NeonColor;
  /** A lit rocker, a toggle under a safety cover, or a key switch. */
  variant?: keyof typeof variants;
  size?: keyof typeof sizes;
  checked?: boolean;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
  /** Engraved legend under the switch. Clicking it flips the switch. */
  label?: React.ReactNode;
  /** Submits `value` under this name when on, like a native checkbox. */
  name?: string;
  value?: string;
}

const HardwareSwitch = React.forwardRef<HTMLButtonElement, HardwareSwitchProps>(
  (
    {
      className,
      style,
      color,
      variant = "rocker",
      size = "md",
      checked: checkedProp,
      defaultChecked = false,
      onCheckedChange,
      onClick,
      disabled,
      label,
      name,
      value = "on",
      id: idProp,
      "aria-labelledby": ariaLabelledBy,
      ...props
    },
    ref,
  ) => {
    const autoId = React.useId();
    const id = idProp ?? autoId;
    const labelId = `${id}-label`;

    const [uncontrolled, setUncontrolled] = React.useState(defaultChecked);
    const isControlled = checkedProp !== undefined;
    const checked = isControlled ? checkedProp : uncontrolled;

    // Each off-to-on change remounts the lamps so the strike replays;
    // a switch that is already on at mount stays steady.
    const [ignitions, setIgnitions] = React.useState(0);
    const [previous, setPrevious] = React.useState(checked);
    if (previous !== checked) {
      setPrevious(checked);
      if (checked) setIgnitions((count) => count + 1);
    }

    const Device = variants[variant];
    const s = sizes[size];

    return (
      <span
        style={neonVars(color, style)}
        className={cn(
          "[--neon:var(--neon-accent,34_211_238)]",
          "inline-flex w-fit select-none flex-col items-center gap-2.5",
          disabled && "opacity-50",
          className,
        )}
      >
        {ignitions > 0 && <style>{keyframes}</style>}
        <button
          ref={ref}
          type="button"
          role="switch"
          id={id}
          aria-checked={checked}
          aria-labelledby={ariaLabelledBy ?? (label ? labelId : undefined)}
          data-state={checked ? "checked" : "unchecked"}
          data-variant={variant}
          disabled={disabled}
          {...props}
          onClick={(event) => {
            onClick?.(event);
            if (event.defaultPrevented) return;
            if (!isControlled) setUncontrolled(!checked);
            onCheckedChange?.(!checked);
          }}
          className={cn(
            "group/hw relative block shrink-0 cursor-pointer outline-none",
            // The open guard swings up above the base, so leave it room.
            variant === "flip" && "mt-[1.6em]",
            variant === "key" ? "rounded-full" : "rounded-[0.5em]",
            "focus-visible:ring-2 focus-visible:ring-[rgb(var(--neon)/0.6)] focus-visible:ring-offset-2 focus-visible:ring-offset-[#05060a]",
            "disabled:cursor-not-allowed",
            s.device,
          )}
        >
          <Device checked={checked} lit={!disabled} ignitions={ignitions} />
        </button>
        {label && (
          <label
            id={labelId}
            htmlFor={id}
            className={cn(
              "cursor-pointer font-medium leading-none tracking-[0.01em] text-white/60",
              disabled && "cursor-not-allowed",
              s.label,
            )}
          >
            {label}
          </label>
        )}
        {name && (
          <input type="checkbox" hidden name={name} value={value} checked={checked} disabled={disabled} readOnly />
        )}
      </span>
    );
  },
);
HardwareSwitch.displayName = "HardwareSwitch";

export { HardwareSwitch };
```
