A transition answers a change of state, and the state comes first: the new page is in the DOM in the next frame, and the motion plays over it. Nothing waits for an animation, nothing that takes taps ever covers the page, and a key pressed during an arrival finishes the arrival before it acts.
How a route changes on this site#
Links here are soft navigations: the App Router renders the next page at once and swaps the page's DOM, so the header, the sidebar, the cursor and the sound stay put. Then:
- At Press and Rave, the page wipe sweeps three bands across the view while the new page renders under it. It takes no pointer events, so a tap during the sweep lands on the new page.
- The head lands: the title rises and fades in, its second ink registers, then the eyebrow, the lede and the chips rise after it.
- The plates arrive (example frames, tables, code, callouts) as panels, the ones in view at once with the stagger, the rest as you scroll. Running text never slides; it fades in under the head.
- Focus moves to the new title, so a screen reader and a keyboard start at the top of the new page.
At Quiet the page simply fades in (160 ms); under reduced motion it crossfades in 140 ms. There are no View Transitions for routes anywhere in the system: they lost taps and started late in Chromium and WebKit.
Page wipe
A plain click on a link to another page. The new page renders under it; it takes no taps.
- Quiet
- None
- Studio
- None: the page arrives (title, panels)
- Press
- Three bands in the field's inks with halftone fringes sweep across once, 410 ms (
wipein,wipe-exitout) - Rave
- 480 ms
- Reduced motion
- None. The docs crossfade the new page in 140 ms instead.
- Sound
- flip
- Tokens
--mk-dur-wipe--mk-dur-reduced--mk-ease-wipe--mk-ease-wipe-exit- Keyframes
mk-page-wipe- Who plays it
- PageWipe · @maxgrits/overprint-react/motion · Page wipe
Use it Over client-side route changes on a loud site or section.
Not when View Transitions for routes (they lose taps); a wipe that waits for the page or covers a form.
Title arrival
A soft navigation (the runtime sets html[data-mk-nav]). The hard load's title never fades: it holds the LCP.
Case study
Fernlight
A booking site for a plant shop in Leith, live in six weeks.
- Quiet
- 160 ms fade
- Studio
- Rises 0.12 em and fades in, 360 ms on
title - Press
- 440 ms, plus a width stretch-in on a display title of three words or fewer
- Rave
- 520 ms, plus the stretch-in
- Reduced motion
- None: the title is there.
- Sound
- None
- Tokens
--mk-dur-arrival--mk-ease-title- Keyframes
mk-title-inmk-stretch-in- Who plays it
- MotionRuntime and the kit's
mk-title-in· @maxgrits/overprint-react/motion · PageHero, SectionHead
Use it On the page's one title after a client navigation.
Not when On a hard load, on body text, or on more than one heading per page.
Second-ink register
A display line with the second ink enters the view (on a hard load too).
Websites that win
- Quiet
- Off
- Studio
- The second ink lands three times its rest offset away and snaps to rest in 520 ms on
register, then stays - Press and Rave
- Same, and the cyan and yellow plates collapse with it
- Reduced motion
- At rest at once.
- Sound
- None
- Tokens
--mk-dur-register--mk-ease-register- Keyframes
mk-snapmk-misreg-in- Who plays it
- Misregistration · @maxgrits/overprint-react/print · Misregistration and the second ink, PageHero
Use it On display type of 2 rem and up: page titles, hero lines, splash titles.
Not when On body text, labels, names or numerals in rows: at small sizes the offset reads as blur.
Panel entrance
A [data-mk-reveal="panel"] plate enters the view, once (the pre-paint script marks it, before React).
Fernlight
A booking site for a plant shop, live in six weeks.
Pebblepath
Trail maps that work offline, for the Saturday walkers.
Sam Okafor Joinery
Quotes in a day: one form, one price, one call back.
- Quiet
- None
- Studio
- Rises 24 px and fades in, settles 2 px past rest, 300 ms on
out, 40 ms apart - Press
- Also scales 0.97 to 1.01 and turns from −0.8°, 360 ms, 48 ms apart
- Rave
- From 40 px, −2° and 0.94 with an overshoot, 420 ms on
slap, 55 ms apart - Reduced motion
- None: every panel is there.
- Sound
- None
- Tokens
--mk-dur-panel--mk-dur-panel-stagger--mk-ease-out--mk-ease-slap- Keyframes
mk-panel-in-studiomk-panel-in-pressmk-panel-in-rave
Use it Plates and cards arriving as they scroll into view; at most eight per batch.
Not when On running text without a plate, on controls, or scrubbed with the scroll.
Press run
The first visit to a boot path (the home). Any key, click or touch skips it.
- Quiet
- None
- Studio to Rave
- 800 ms: the mark's four plates print 120 ms apart, each landing 6 px out of register and snapping in, then the paper wipes off in 300 ms
- Reduced motion
- None: the page shows at once.
- Sound
- None (a first visit has had no gesture to unlock audio)
- Tokens
--mk-dur-press-run--mk-dur-reduced--mk-ease-register--mk-ease-snap--mk-ease-squeegee- Keyframes
mk-press-run-platemk-press-run-wipemk-press-run-failsafe- Who plays it
- PressRun · @maxgrits/overprint-react/motion · Press run
Use it Once, on the first visit to the front door of a site.
Not when On every page or every visit, behind a form, or anywhere a reader came to read fast.
Re-ink and theme
The visitor turns the volume dial, picks a colourway or swaps the paper.
- Quiet
- 400 ms crossfade
- Studio to Rave
- 700 ms colourway, 400 ms paper; the field and the 3D re-ink with it
- Reduced motion
- A 140 ms crossfade.
- Sound
- None
- Tokens
--mk-dur-reink--mk-dur-theme--mk-ease-in-out- Who plays it
- VolumeDial, ThemeToggle and the tokens · @maxgrits/overprint-react · ThemeToggle, VolumeDial, VisitorControls
Use it When the reader changes the look themselves.
Not when On a timer or on scroll: the look changes only when someone asks.
Overlays, toasts and disclosures#
The reading layer moves the same at every volume: these never get louder with the dial.
Toast
Your code raises a toast; it leaves on its own after its life, or when dismissed.
- Every volume
- Drops in 380 ms, drains over 5.2 s, leaves in 260 ms
- Reduced motion
- Fades in and out in 140 ms; the drain still counts down.
- Sound
- None
- Tokens
--mk-dur-toast-in--mk-dur-toast-out--mk-dur-toast-life--mk-dur-reduced--mk-ease-out- Keyframes
mk-toast-inmk-toast-drainmk-toast-fade- Who plays it
- Toast · @maxgrits/overprint-react/toast · Toast
Use it A short confirmation with an undo.
Not when Errors that need a decision, or anything the reader must read twice.
Disclosure
The reader opens or closes it.
Do I own the code?
How long does a site take?
- Every volume
- Unfolds in 180 ms, the chevron turns
- Reduced motion
- Opens at once.
- Sound
- None
- Tokens
--mk-dur-disclosure--mk-ease-out- Who plays it
- Disclosure and Faq · @maxgrits/overprint-react/disclosure · Disclosure
Use it Answers and details the reader asks for.
Not when Hiding the one thing every reader needs.
Dialog
The reader opens it; Escape or Close closes it.
- Every volume
- In 240 ms, out 160 ms
- Reduced motion
- Fades in 140 ms.
- Sound
- None
- Tokens
--mk-dur-overlay-in--mk-dur-overlay-out--mk-dur-reduced--mk-ease-out- Who plays it
- Dialog · @maxgrits/overprint-react/dialog · Dialog
Use it A decision that needs the reader before anything else.
Not when Messages that could sit on the page.
Drawer
The reader opens it.
- Every volume
- Slides in 280 ms, out 200 ms
- Reduced motion
- Fades in 140 ms.
- Sound
- None
- Tokens
--mk-dur-drawer-in--mk-dur-drawer-out--mk-dur-reduced--mk-ease-out
Use it A side task: a quote form, the phone menu.
Not when Content that belongs on its own page.
The reader opens a menu or a nav panel.
- Every volume
- Menus in 140 ms; the nav panel unfolds in 180 ms
- Reduced motion
- The panel fades in 140 ms.
- Sound
- None
- Tokens
--mk-dur-menu--mk-dur-disclosure--mk-dur-reduced--mk-ease-out- Keyframes
mk-navbar-panel-inmk-navbar-panel-fade
Use it Every menu and nav panel.
Not when Menus that open on hover alone.
Keys and gauges#
Hover and press
The pointer rests on a control; a press goes down.
- Quiet
- Colour only
- Studio to Rave
- Colour, a lift of 2 px or less; a key sinks 2 px into its offset in 120 ms
- Reduced motion
- Colour only.
- Sound
- None
- Tokens
--mk-dur-hover--mk-dur-press--mk-dur-key-sink--mk-ease-out--mk-ease-pop- Who plays it
- Button and every control · @maxgrits/overprint-react · Button, IconButton, Card, Link
Use it Every control says it can be pressed, and every press is felt.
Not when Hover that moves words, grows a box or reflows the layout.
A key's action is under way (loading).
- Every volume
- The label stays; a 2 px bar in its ink draws under it every 1.2 s while it waits
- Reduced motion
- No bar; the key stays busy (
aria-busy) and its label says what is happening. - Sound
- None
- Tokens
--mk-dur-reduced--mk-ease-out- Keyframes
mk-button-pending- Who plays it
- Button · @maxgrits/overprint-react/button · Button
Use it While a form sends or a payment confirms.
Not when As decoration: it runs only while something is really pending.
Gauge
The dial enters the view: its arc draws to the score.
- Every volume
- Draws in 900 ms
- Reduced motion
- Drawn at once.
- Sound
- None
- Tokens
--mk-dur-gauge--mk-ease-out- Keyframes
mk-score-dial-draw- Who plays it
- ScoreDial · @maxgrits/overprint-react/score-dial · ScoreDial
Use it One score that was earned (a Lighthouse result).
Not when Dials for every number on a page.
Loader
Something is loading; the label stays still beside the glyphs.
- Every volume
- Three ink glyphs hop in turn, 900 ms a cycle, 150 ms apart, five cycles at most, then still
- Reduced motion
- Static, with its words.
- Sound
- None
- Tokens
- None
--mk-ease-pop - Keyframes
mk-loader-hop- Who plays it
- Loader · @maxgrits/overprint-react/loader · Loader
Use it A wait of more than about a second, with a label.
Not when Waits under a second (show the result), or a loader with no words.