2 min read
UI at 12fps
What if UI moved like Spider-Verse?
I posted a little video of this on X (opens in new tab) and it got a lot more attention than I expected, so here is the slightly longer version. These are real Base UI (opens in new tab) components animated at 12fps, the same way Spider-Verse animates its characters. Have a click around.
01Dialog5 in · 3 out
02Menu4 + 1 per item
03Popover4 in · 2 out
04Tooltip4 in · 2 out
05Switch & checkbox4 · 3
06Tabs4
Base UI does state and a11y.
CSS draws every pose.
No animation library.
07Accordion4
08Toast4 in · 2 out
What is “on twos”?
Film runs at 24fps. Animating on twos means each drawing is held for two of those frames, so the character only moves 12 times a second. Spider-Verse does this to the characters while the camera stays smooth, which is a big part of why it looks drawn rather than rendered.
UI usually does the opposite and interpolates every frame, keeping the motion as subtle as possible. I wanted to see what happens if you don’t.
Just drop the frames
The obvious first attempt is to take a normal, subtle animation and hold each frame. Both of these take the same amount of time, the one on the right just gets 2 drawings to do it in, and it ends up looking laggy rather than drawn.
Smoothevery frame
Delete this project?
You can’t undo this.
Laggy12fps
Delete this project?
You can’t undo this.
Push the poses
Animators get around this by exaggerating. Each drawing is a pose, squashed, overshooting, stretched and then settled. Same 12fps, but now every frame actually has something to say.
Laggy12fps
Delete this project?
You can’t undo this.
On twos12fps, big poses
Delete this project?
You can’t undo this.
Why not just go cartoon?
You could keep the big poses and interpolate between them. That’s the left side, it’s bouncy and fun but a bit floaty because the in-betweens smooth out all of the snap. Holding the poses on the right feels punchier, even with the same keyframes and the same timing.
Cartoonevery frame, big poses
On twos12fps, big poses
How it works
There is no animation library involved. Base UI handles the state and accessibility and exposes data attributes like data-open and data-closed, then CSS draws every pose.
Each keyframe stop is one drawing and steps(1, end) holds it until the next one, so the duration is just the number of drawings divided by the frame rate.
.dialog[data-open] {
/* 5 drawings at 12fps */
animation: pop-in calc(5s / 12) steps(1, end) both;
}
@keyframes pop-in {
0% { transform: scale(0.45, 0.3) translateY(24px); }
20% { transform: scale(1.14, 0.84); }
40% { transform: scale(0.9, 1.12) translateY(-8px); }
60% { transform: scale(1.04, 0.97); }
80%, 100% { transform: none; }
}Here are those 5 drawings, step through them one at a time. The faded copies are the earlier drawings, like onion skinning in an animation tool, so you can see how far the dialog jumps from one drawing to the next.
transform: scale(0.45, 0.3) translateY(24px)Transitions are a little trickier as steps() can’t overshoot, but linear() can, and if you repeat each stop you get a held frame. That’s what drives the tabs indicator and the accordion.
.tab-indicator {
translate: var(--active-tab-left) 0;
transition: translate calc(4s / 12)
linear(0.3 0%, 0.3 25%, 1.2 25%, 1.2 50%, 0.94 50%, 0.94 75%, 1 75%, 1 100%);
}What stays smooth
Anything you’re touching. If the slider thumb only moved 12 times a second it would just feel laggy, no matter how good the poses are. The thumb follows your pointer every frame and only the grab and the value bubble are drawn.
Spider-Verse keeps the camera smooth, UI should keep the input smooth.
Got any questions? Feel free to reach out on X - @danbillson (opens in new tab)