Shades

No dependencies
Code format
React + Tailwind
import { useId } from 'react';

const css = `
  .toggle-shades { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
  .toggle-shades__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
  .toggle-shades__art { display: block; width: 124px; height: 50px; overflow: visible; border-radius: 8px; }
  .toggle-shades__input:focus-visible + .toggle-shades__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
  .toggle-shades__glass { fill: light-dark(#dbdde1, #7d8087); }
  .toggle-shades__eye { fill: light-dark(#373a40, #121417); }
  .toggle-shades__frame { fill: none; stroke: light-dark(#50535b, #c6c8cd); stroke-width: 2.6; stroke-linejoin: round; stroke-linecap: round; }
  /* The tint is dark in both themes. It runs down from the top like a photochromic lens. */
  .toggle-shades__top { stop-color: light-dark(#50535b, #393c41); }
  .toggle-shades__bottom { stop-color: light-dark(#373a40, #121417); }
  .toggle-shades__shine { fill: none; stroke: light-dark(#fbfcfd, #f1f2f3); stroke-width: 1.6; stroke-linecap: round; opacity: 0; transition: opacity 0.3s; }
  .toggle-shades__input:checked + .toggle-shades__art .toggle-shades__shine { opacity: 0.35; transition-delay: 0.4s; }
  .toggle-shades__tint { transform-box: view-box; transform-origin: 0 12px; transform: scaleY(0); transition: transform 0.55s cubic-bezier(0.5, 0, 0.3, 1); }
  .toggle-shades__glint { fill: light-dark(#fbfcfd, #f1f2f3); opacity: 0.4; transform: translateX(-70px) skewX(-25deg); transition: transform 0.4s ease-in; }
  .toggle-shades__input:checked + .toggle-shades__art .toggle-shades__tint { transform: none; }
  .toggle-shades__input:checked + .toggle-shades__art .toggle-shades__glint { transform: translateX(110px) skewX(-25deg); transition: transform 0.7s cubic-bezier(0.4, 0, 0.6, 1) 0.45s; }
  @media (prefers-reduced-motion: reduce) {
    .toggle-shades__tint, .toggle-shades__glint, .toggle-shades__input:checked + .toggle-shades__art .toggle-shades__glint { transition: none; }
  }
`;

const LENS = 'M10 12H50Q53 12 52 16L49 30Q47 37 39 37H22Q12 37 11 28Z';
const LENS_R = 'M112 12H72Q69 12 70 16L73 30Q75 37 83 37H100Q110 37 111 28Z';

export function ShadesToggle() {
  const clip = `toggle-shades-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
  return (
    <>
      <style>
        {css}
      </style>
      <label className="toggle-shades">
        <input className="toggle-shades__input" type="checkbox" role="switch" />
        <svg className="toggle-shades__art" viewBox="0 0 122 48" aria-hidden="true">
          <defs>
            <linearGradient id={`${clip}-tint`} x1="0" y1="0" x2="0" y2="1">
              <stop className="toggle-shades__top" offset="0" />
              <stop className="toggle-shades__bottom" offset="0.75" />
            </linearGradient>
            <clipPath id={clip}>
              <path d={LENS} />
              <path d={LENS_R} />
            </clipPath>
          </defs>
          <path className="toggle-shades__glass" d={LENS} />
          <path className="toggle-shades__glass" d={LENS_R} />
          <g clipPath={`url(#${clip})`}>
            <circle className="toggle-shades__eye" cx="31" cy="24" r="3" />
            <circle className="toggle-shades__eye" cx="91" cy="24" r="3" />
            <rect className="toggle-shades__tint" fill={`url(#${clip}-tint)`} x="0" y="12" width="122" height="26" />
            <path className="toggle-shades__shine" d="M17 30L24 16M80 30L87 16" />
            <rect className="toggle-shades__glint" x="20" y="8" width="7" height="34" />
          </g>
          <path className="toggle-shades__frame" d={LENS} />
          <path className="toggle-shades__frame" d={LENS_R} />
          <path className="toggle-shades__frame" d="M52 16Q61 10 70 16M3 13H10M112 13H119" />
        </svg>
        <span>Dark mode</span>
      </label>
    </>
  );
}

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

Install with shadcn

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

About this component

A dark mode switch drawn as sunglasses: the lenses tint from the top down like photochromic glass and a glint slides across them.

Dependencies
None
Difficulty
Intermediate
Triggers
click, state change, gradient
Techniques
svg, gradient, css transition
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 the visible text names it; :focus-visible on the input outlines the drawing, which is aria-hidden. Under prefers-reduced-motion the lenses tint at once.

More toggles

See all
Shell
Sign
Signal
Turntable