Animation Specimens

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.

Your browser, right now

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.

Kestrel Roofing

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.

This panel is 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.

Twelve years on the same roofs

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.

A fixed price before anyone climbs a ladder.

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.

KESTREL ROOFING

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.

82%

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.

linear
ease-in-out
cubic-bezier
linear() spring

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.