Webcam

No dependencies
Code format
React + Tailwind
const css = `
  .toggle-webcam { position: relative; display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 14px; cursor: pointer; user-select: none; }
  .toggle-webcam__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
  .toggle-webcam__scene { position: relative; flex: none; }
  .toggle-webcam__input:focus-visible + .toggle-webcam__scene { outline: 2px solid light-dark(#50535b, #c6c8cd); outline-offset: 4px; border-radius: 8px; }
  .toggle-webcam__scene { width: 84px; height: 30px; border-radius: 15px; background: light-dark(#f3f4f7, #1a1c1f); box-shadow: inset 0 0 0 1px light-dark(#cbced3, #393c41); }
  .toggle-webcam__lens { position: absolute; left: 34px; top: 7px; width: 16px; height: 16px; border-radius: 50%; background: radial-gradient(circle at 38% 35%, light-dark(#6d7179, #7d8087) 0 2px, light-dark(#fbfcfd, #121417) 2.5px 4px, light-dark(#dbdde1, #2e3035) 4.5px 5.5px, light-dark(#fbfcfd, #121417) 6px); box-shadow: 0 0 0 1px light-dark(#cbced3, #393c41); }
  .toggle-webcam__led { position: absolute; left: 56px; top: 13px; width: 4px; height: 4px; border-radius: 50%; background: light-dark(#dbdde1, #2e3035); transition: background-color 0.2s 0.3s, box-shadow 0.2s 0.3s; }
  /* The privacy shutter: a ridged slider that covers the lens, and slides aside to switch the camera on. */
  .toggle-webcam__shutter { position: absolute; left: 29px; top: 4px; width: 26px; height: 22px; border-radius: 6px; background: repeating-linear-gradient(90deg, light-dark(#5f626a, #a7abb2) 0 2px, light-dark(#6d7179, #7d8087) 2px 4px); box-shadow: 0 2px 4px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.4)); transition: transform 0.45s cubic-bezier(0.3, 1.3, 0.5, 1); }
  .toggle-webcam__track { position: absolute; left: 6px; right: 6px; top: 14px; height: 2px; border-radius: 1px; background: light-dark(#ebedf0, #232529); }
  .toggle-webcam__input:checked + .toggle-webcam__scene .toggle-webcam__shutter { transform: translateX(-23px); }
  .toggle-webcam__input:checked + .toggle-webcam__scene .toggle-webcam__led { background: light-dark(#373a40, #f1f2f3); box-shadow: 0 0 6px light-dark(#373a40, #f1f2f3); }
  @media (prefers-reduced-motion: reduce) {
    .toggle-webcam__shutter, .toggle-webcam__led { transition: none; }
  }
`;

export function WebcamToggle() {
  return (
    <>
      <style>
        {css}
      </style>
      <label className="toggle-webcam">
        <input className="toggle-webcam__input" type="checkbox" role="switch" />
        <span className="toggle-webcam__scene" aria-hidden="true">
          <span className="toggle-webcam__track" />
          <span className="toggle-webcam__lens" />
          <span className="toggle-webcam__led" />
          <span className="toggle-webcam__shutter" />
        </span>
        Camera
      </label>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/toggle-webcam
Source on GitHub

About this component

A camera switch drawn as a laptop webcam: the ridged privacy shutter slides off the lens and the camera light comes on; slide it back to cover it.

Dependencies
None
Difficulty
Beginner
Triggers
click, state change, glow
Techniques
css transition, gradient, transform only
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

A native checkbox with role="switch" inside a <label>, so Space toggles it and screen readers announce a switch; the visible label names it and :focus-visible on the input outlines the visual. Under prefers-reduced-motion the shutter moves instantly.

More toggles

See all
Arc
Binary
Bloom
Cassette