Eight ways type can move, and one place it may not.
Headline and callout motion as working specimens. Play each one, read the wiring line, and copy the markup, never a screenshot. Every piece is one clause of the motion register, demonstrated at full behaviour.
Nothing on this page loops or autoplays, and this headline does not move. Both are the law: one loud piece per page, and the line that tells you where you are stays still.
What kinetic type may do here
- It may not touch a control. Flourish goes around buttons, links and fields, never on them. A moving target is a missed click. → K08
- It may not deliver the content. The words are in the markup and readable at rest, mid-play, and with JS off. Motion is applied over finished prose. → K06
- It may not orient the reader. Callouts, section moments and brand heroes are fair game. The page header that says where you are holds still. → K07
- It may not run on a clock where someone is reading. On an informational surface, motion keys to scroll and rests when the reader rests. → K04
- It may only move
transformandopacity. Compositor properties, so a phone never janks. No libraries, no canvas: the estate has no build step and keeps it that way. → K01 - It may not be the only copy of the sentence. Split layers carry
aria-hidden; a plain twin underneath keeps the prose whole for a screen reader. → K02
Mask rise
in the kitregister rule 7 · transform only, no layout animation
The workhorse. A line sits inside a frame with overflow: hidden and rises into it from below. Nothing animates height or margin, so the paragraph beneath never shifts: the frame is already the finished size before the word arrives. This is the entrance every other specimen is built from.
.kt-mask { overflow: hidden } wraps .kt-line { animation: kt-rise } · @keyframes kt-rise { from { transform: translateY(105%) } to { transform: translateY(0) } } · 105% not 100% so descenders clear the frame · the mask carries aria-hidden, a .vh twin holds the sentence
Word stagger vs line rise
open pickthe decision on this page · both are legal, one becomes the default
The same hero, two treatments, playing together so the difference is visible rather than described. A staggers word by word at 45ms; B moves each line as one object. A reads as assembly and draws more attention; B reads as arrival and gets out of the way faster. Stagger is capped either way: ten words at 45ms is 450ms of entrance, and past that a reader is waiting on the page.
stagger: style="--i:N" per word · animation-delay: calc(var(--i) * 45ms) · cap the count, not the delay: 10 words is the ceiling, past that reduce the step · one .vh twin per block, not per word — a screen reader should hear a sentence, not eleven fragments
The seal lands
in the kitregister rule 1 · the ring sits beside the Code, never on the control
A Code arriving on a share panel. The characters settle rather than assemble: a 6% scale and a fade, 34ms apart, then a single ring answers next to the seal once the last character lands. The restraint is deliberate and it is a brand rule, not taste. The Code is the wrapper, not the gift, so its arrival is an acknowledgement and never a fanfare. Where sharing is the subject the seal carries the weight of a name that matters, which is what this is: no confetti, no spin, one ring.
per-character split needs aria-hidden and a .vh sentence — character spans read letter-by-letter otherwise · kt-settle: opacity + scale(.94 → 1), never rotate · the ring is a sibling of the Code, never a wrapper · presence law: briefs/BRIEF.code-presence.md
Scroll-keyed lines
in the kitregister rule 6 · scroll-driven over time-driven where someone is reading
On a page someone is reading, motion answers to the reader instead of a timer: it advances when they advance and rests when they rest. Scroll inside the frame below and the lines key to your position, backwards as well as forwards. This runs on CSS scroll timelines with no script and no observer, and where the browser has no support the lines simply rest finished, which is the correct frame anyway.
on the line: animation-timeline: view() — names nothing, tracks the element in its nearest scrollport · animation-range: entry 12% cover 40% · wrap in @supports (animation-timeline: view()) + prefers-reduced-motion: no-preference, so no-support and reduced-motion both land on the finished line · no JS, no IntersectionObserver, no library
Word swap in place
in the kitOne word changes while the sentence around it holds still. The alternates are stacked in a clipped frame and the stack translates by exactly one line height, so the frame never resizes and the words on either side never reflow. The four here are the product’s own loop steps, so the mechanism is carrying real copy rather than lorem. Step it manually: this is a callout moment, not a carousel, and it never runs on its own.
Four steps: connect, package, share, then relay it in.
frame: .kt-swap { overflow: hidden; height: 1.2em } · stack: transform: translateY(calc(var(--kt-slot) * -1.2em)) · the em step and the frame height are the same number, or the words drift · transition, not an animation loop — a swap that runs itself is a carousel and breaks rule 4
The resting frame
in the kitregister rule 3 · reduced motion gets the finished frame, never a blank
The specimen that is not a motion. Switch the page into its reduced-motion state and every piece above lands where it would have finished: present, opaque, in place. This is the failure the rule exists to catch, because the natural way to write an entrance leaves the element at opacity: 0 when the animation never runs, and the visitor who asked for less motion gets an empty page instead. Toggle it and scroll back up: nothing disappears.
The switch adds one class to <html> and mirrors what the media query does for real. It is here so the frame can be inspected without changing an operating-system setting, which is the reason this failure usually ships unseen.
write entrances so rest is the finished state and the animation only plays under a class · never opacity: 0 in the base rule · the media query sets animation: none; opacity: 1; transform: none · verify by rendering with reduced motion emulated, not by reading the CSS
Where it may not go
rejected patternregister rule 5 · kept live so the rule has a face
A page header assembling itself word by word. It is competent motion and it is banned here, which is the distinction worth keeping visible: brand heroes may move, and the line that tells a reader where they are may not. It was tried on the source estate and pulled the same day.
Example page header: Operations, session coordination.
The assembled callout
in the kitregister rules 1 and 4 · one loud piece, and nothing loud on the control
The pieces working together as a real section would use them: mask rise on the head, the supporting line following at 260ms, and a call to action that does not move at all. This is the whole budget for a page. If a section like this is on the page, everything else stays at whisper tier or waits to be asked.
The idea
Your session doesn't end when the window does. Close the laptop mid-thought. Open a different tool tomorrow. The work you were in is still there, still yours, and loads where you are now. Get accesshead rises, sub follows at 260ms — support never leads · the CTA has no entrance and no transform: it is the target, it holds still · one assembly of this weight per page · copy is canon: copy/relay-web-copy.md