Back to the library

Toggle — flip chip

Variant: Flip

No track at all: a chip that turns over, oat on one face and clay on the other. The press leans 9deg into the turn so the outcome is legible from the trajectory, then the half turn overshoots 7deg and settles. Rotation is the one place a bit of bounce is correct — it is the physical reading of a thing with mass being spun. Use it where a state change deserves to be felt.

control

The source

A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Toggle — flip chip</title>
    <style>
      :root {
        /* Neutrals — fixed, never remixed. */
        --ivory: #faf9f5;
        --paper: #ffffff;
        --oat: #e3dacc;
        --slate: #141413;

        /* Knobs — the remix API. A knob exists iff declared here and used below. */
        --hue: 38.8;
        --lift: 0;
        --chroma: 1;
        --round: 1;
        --speed: 1;

        /* Accents — derived from the knobs with the family's offsets baked in,
           so one hue roll moves them together. Defaults reproduce the original hex. */
        --clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
        --clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
        --olive: oklch(
          calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3)
        );
        --rust: oklch(
          calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3)
        );

        /* Clock — every duration in the document runs off --t. */
        --cycle: 4.2s;
        --t: calc(var(--cycle) / var(--speed));

        --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
        /* The iOS drawer curve: fast off the mark, long settle. */
        --ease-accent: cubic-bezier(0.32, 0.72, 0, 1);
      }

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      body {
        min-height: 100vh;
        display: grid;
        place-items: center;
        background: var(--ivory);
        font-family:
          system-ui,
          -apple-system,
          'Segoe UI',
          Roboto,
          sans-serif;
        color: var(--slate);
      }

      .stage {
        width: 84px;
        height: 84px;
        perspective: 520px;
      }

      .chip {
        position: relative;
        width: 100%;
        height: 100%;
        transform-style: preserve-3d;
        animation: chip-flip var(--t) infinite;
      }

      .face {
        position: absolute;
        inset: 0;
        display: grid;
        place-items: center;
        border-radius: calc(24px * var(--round));
        backface-visibility: hidden;
        box-shadow: 0 2px 6px color-mix(in srgb, var(--slate) 14%, transparent);
      }

      .face.off {
        background: var(--oat);
      }

      .face.on {
        background: var(--clay);
        transform: rotateY(180deg);
      }

      .bar {
        width: 28px;
        height: 4px;
        border-radius: 2px;
        background: var(--slate);
        opacity: 0.55;
      }

      .tick {
        width: 34px;
        height: 34px;
        fill: none;
        stroke: var(--ivory);
        stroke-width: 3.5;
        stroke-linecap: round;
        stroke-linejoin: round;
      }

      /* The press leans 9deg into the turn before it starts: a trajectory the
         eye can read the outcome from, rather than a state that just swaps.
         Rotation is the one place Apple ships damping below 1.0, so the half
         turn overshoots 7deg and settles back — 143ms of anticipation, 256ms
         out, 109ms to rest.
         Turning it back off is not that half-turn played in reverse. Switching
         something on is a decision and gets the full lean; switching it off is
         a dismissal, so the anticipation drops to 101ms, the turn to 214ms, the
         overshoot from 7deg to 4deg and the settle to 84ms. Same gesture, less
         ceremony — 399ms against 508ms. */
      @keyframes chip-flip {
        0%,
        9.5% {
          transform: rotateY(0deg) scale(1);
          animation-timing-function: var(--ease-out);
        }
        12.9% {
          transform: rotateY(9deg) scale(0.95);
          animation-timing-function: var(--ease-accent);
        }
        19% {
          transform: rotateY(187deg) scale(1);
          animation-timing-function: var(--ease-out);
        }
        21.6%,
        52.4% {
          transform: rotateY(180deg) scale(1);
          animation-timing-function: var(--ease-out);
        }
        54.8% {
          transform: rotateY(174deg) scale(0.96);
          animation-timing-function: var(--ease-accent);
        }
        59.9% {
          transform: rotateY(-4deg) scale(1);
          animation-timing-function: var(--ease-out);
        }
        61.9%,
        100% {
          transform: rotateY(0deg) scale(1);
        }
      }

      @media (prefers-reduced-motion: reduce) {
        /* No axis to spin around: both faces sit flat and cross-fade, which
           keeps the colour change that carries the meaning. */
        .chip {
          animation: none;
        }

        .face {
          backface-visibility: visible;
        }

        .face.on {
          transform: none;
          animation: face-in var(--t) ease infinite;
        }

        @keyframes face-in {
          0%,
          12.9% {
            opacity: 0;
          }
          19%,
          54.8% {
            opacity: 1;
          }
          59.9%,
          100% {
            opacity: 0;
          }
        }
      }
    </style>
  </head>
  <body>
    <div class="stage" role="img" aria-label="State chip flipping between off and on">
      <div class="chip">
        <div class="face off"><div class="bar"></div></div>
        <div class="face on">
          <svg class="tick" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M5 12.5 L10 17.5 L19 7" />
          </svg>
        </div>
      </div>
    </div>
  </body>
</html>

The variant family

Same idea, one axis moved. Compare them side by side before you commit.