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 {
--ivory: #faf9f5;
--slate: #141413;
--clay: #d97757;
--oat: #e3dacc;
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--ease-drawer: 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 4.2s infinite;
}
.face {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 24px;
backface-visibility: hidden;
box-shadow: 0 2px 6px rgba(20, 20, 19, 0.14);
}
.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-drawer);
}
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-drawer);
}
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 4.2s 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.