The second working surface for the motion system. Motion Specimens answers what scrolling can drive; this one covers everything else — pointer, entrance, text, shape, ambient, data and easing. Twenty-one effects, each labeled with what it costs.
Free means pure CSS, authoring only. Behavior means it needs a piece of code in pattern_render.js, because templates ship no JavaScript. Every effect marked behavior follows the same shape: the runtime reports something — an index, a pointer position, a moment — and the stylesheet decides what it means. Nothing here is decided; see spec/accelerate-motion-system.md.
Pointer
01 — Card lift
transform + box-shadow on :hover — free
The shadow steps up with the movement. A card that rises without its shadow changing reads as sliding rather than lifting — the shadow is what says how far off the page it has come.
Hero Feature
Hover me.
CTA Band
Hover me.
Statement
Hover me.
The cheapest craft on this page. Already used across the showpiece wave.
02 — Zoom inside the frame
overflow on the frame, transform on the picture — free
The frame never moves; only the picture inside it does. That is what keeps a row of cards level while one of them is being hovered — scaling the frame itself would push its neighbors around.
Wants aspect-ratio on the frame so the row stays level whatever the buyer uploads.
03 — Underline wipe
scaleX from transform-origin: left — free
The line grows out of the first letter rather than appearing all at once, and because it is a pseudo-element being scaled, nothing reflows. A border that appears on hover shifts the line it sits on.
Pair with :focus-visible, as here, or keyboard users get nothing.
04 — Arrow nudge
translateX on a child span — free
A quarter of an em is enough. The arrow moves, the words do not, so the link stays readable while it is being pointed at.
Reads as responsiveness rather than decoration, which is why it survives on serious sites.
05 — Tilt toward the pointer
runtime writes --mx / --my, CSS rotates — behavior
The script writes two numbers between -1 and 1; the stylesheet decides they mean seven degrees of rotation. That split is the pattern every pointer effect here follows — JavaScript reports, CSS decides.
Point at me
Two numbers, seven degrees.
Keep it under about eight degrees. Past that it stops reading as depth and starts reading as a bug.
06 — Spotlight follow
runtime writes --px / --py into a radial gradient — behavior
A soft light that tracks the pointer across a panel. Same mechanism as the tilt: coordinates in, gradient position out. It fades in on hover so it never appears on a page nobody is touching.
Move across me
The light follows the pointer.
Touch devices never hover, so this must never be the only affordance.
Entrance
07 — Entry with no trigger at all
@starting-style — free
The entry state is declared rather than triggered. No observer, no keyframes, no class — the browser interpolates from the starting style the first time the element is rendered. If this is well enough supported, it removes the need for a reveal behavior on anything above the fold. Reload the page to see it again.
I arrived
No JavaScript was involved.
So did I
Declared, not triggered.
Test this before building a reveal behavior. If the panel above says it is supported, a good deal of the motion system gets cheaper.
08 — Letter stagger
one span per letter carrying --i — behavior
The delay is calc(var(--i) * 40ms) — one declaration, any number of letters, no per-position rules. This is the --accpt-i proposal in miniature: the runtime supplies the index, the sheet does the arithmetic. The same shape would serve a collection of cards.
The splitter must set aria-label to the original string, or a screen reader reads it letter by letter.
09 — Disclosure that animates from display:none
transition-behavior: allow-discrete — free
Until this existed, a panel going from display:none snapped: the transition never ran, because display is a discrete property. allow-discrete makes it animatable, which means the accordion and tabs behaviors we already ship could open and close properly rather than jumping.
display: none when closed. It fades rather than snapping, which a discrete property could not do before.Improves two shipped behaviors rather than adding one. Worth testing early.
Text
10 — Sheen across the letterforms
background-clip: text + moving gradient — free
The gradient is painted through the letters rather than over them, so the shine belongs to the type. Slow and low contrast, or it reads as a novelty.
Needs a real color fallback: color: transparent with no clip support is invisible text.
11 — Typewriter
steps() on width, plus a caret — free
The step count has to match the character count exactly or the text lands mid-letter. That is fiddly enough by hand to be an argument for a module: the renderer knows the string length and can write both numbers.
Only works on a single line of monospace-ish text. Wrapping breaks it entirely.
12 — Scramble to settle
JavaScript, replacing characters on a timer — behavior
Each character settles at its own moment, so the word assembles rather than fading. Nothing in CSS can do this — it is text replacement, not styling.
Set the final string in the markup so it is correct with the script disabled.
Shape
13 — Mask wipe
a gradient used as a mask, moved across — free
The reveal has a soft edge, which no clip-path can give — a clip is a hard boundary. Here the mask is a gradient three times the width of the element, slid from one side to the other.
Surveyed
Wiped in.
Quoted
Soft edge.
Finished
No clip.
Still needs the -webkit- prefix alongside the standard property.
14 — Iris
clip-path circle opening from a point — free
A hard-edged reveal expanding from wherever you choose. Useful when the thing being revealed has a natural center — a logo, a figure, a portrait.
Opened from the middle
clip-path, not opacity.
Percentages on circle() are relative to the diagonal, so 75% covers a rectangle comfortably.
15 — Border draw
stroke-dashoffset on an SVG — free
The one way to draw a line along its path rather than fading it in. The dash array is set longer than the perimeter and the offset is animated to zero.
The dash array is the path’s measured length, set by the script with getTotalLength(). Guessing it is always wrong: short and the line never closes, long and the draw starts late.
Ambient
16 — Rotating conic border
@property on an angle — free
An angle is not animatable until it is registered with @property — without that the gradient jumps between keyframes instead of turning. Registering custom properties is what makes a whole class of effects possible.
Registered properties
The border turns because the angle can be interpolated.
The same trick animates gradients, numbers and lengths. It is the enabler behind the counters and the progress ring below.
17 — Aurora
three blurred radials at different periods — free
Nineteen, twenty-three and twenty-nine seconds — deliberately not multiples of each other, so the pattern never visibly repeats. A ground for a hero that needs no photograph and no stock library.
Get a fixed price first
No picture. Three gradients and a blur.
A large blur() is the expensive part. Worth measuring on a phone before it ships.
Data
18 — Progress ring
conic-gradient + @property percentage — free
One gradient and one registered property. No SVG, no circumference maths, no stroke offsets — the ring is the gradient’s own sweep, and the hole is a radial gradient painted over the middle.
The figure has to be real text for a screen reader; the ring is decoration over the top of it.
19 — Odometer
per-digit strips translated by whole ems — behavior
Each column holds nought to nine and slides to its digit, so the numbers roll. It reads as a mechanism counting rather than a number being animated, which suits a figure that represents something accumulating.
Different from the count-up in Motion Specimens: that interpolates a value, this moves type. Worth having both.
Easing
20 — The curve is the effect
linear() for springs, without a library — free
Same distance, same duration, four curves. The fourth is linear() with a list of stops approximating a spring — it overshoots and settles, which used to need a physics library. Most motion that feels wrong is the right effect on the wrong curve.
If a motion vocabulary ships, the curves belong in it as named values — not retyped per template.
State
21 — Shimmer placeholder
a sweep across neutral blocks — free
The one loading state worth having. A slow page with a shimmer reads as working; the same page with a blank area reads as broken. Three bars of uneven width look like text without pretending to be it.
Only honest if something is actually loading. A permanent shimmer is a lie about the state of the page.
What this changes
Fifteen of the twenty-one cost nothing but authoring. The six that need code all need the same kind of code — something that reports a number and lets CSS spend it: an index, a pointer position, a moment of arrival. That is one small runtime surface, not six features.
Two are worth testing before anything is built. @starting-style (07) may remove the need for a reveal behavior entirely, and transition-behavior: allow-discrete (09) would improve the accordion and tabs behaviors that already ship rather than adding a new one.