Motion
Okami moves the way a good receptionist does: once to welcome you, then only in response to you. Every motion, from a focus ring to a closing sidebar, times itself on one scale: the motion system of transitions.dev.
Rules
- One orchestrated moment per page. On the chat page it is the venue hero handing over to the compact header; a new page earns exactly one such moment, deliberately chosen.
- Everything else answers an action: a message rising in, a chip scaling under the finger, a focus ring settling. Nothing loops for decoration. The drifting sky is the single sanctioned exception, and it is imperceptible on purpose.
- The scale below is the only timing there is. Pick a step by what the motion does, never by the nearest number: a dialog closing is
duration-quickwhatever it was before. No ad-hocms, no hand-rolledcubic-bezier. - Opening invites, closing gets out of the way: a surface opens on
duration-fastand closes onduration-quick. Never delay a close, and never bounce one. - Choices glide. Where every item can be chosen, they sit close and they stay put — a menu, a select, the sidebar, tabs, a calendar's days — one wash travels to the nearest item instead of each item blinking on and off:
Glidefromshared/glide. Not on tables, sparse cards, lists that move, or anything a wrong click would hurt. - Animate on the compositor:
transformandopacity, never a custom property, never a style read per frame. A blur is the one exception, and only on words (a headline's reveal): on chrome — the nav — it stuttered while the page loaded. The craft timer's ring is the other: one small SVG'sstroke-dashoffset. - Every animation stills under
prefers-reduced-motion. This is part of the definition of done, not an enhancement.
The recipes come from transitions.dev by Jakub Antalik, installed in the repository as the transitions-dev and transitions-polish skills. Ask for transitions review to audit a component against the scale.
Durations
Tailwind's duration-* scale. A bare transition is duration-quick and ease-smooth-out.
duration-staggerduration-microduration-quickduration-fastduration-mediumduration-slowduration-very-slow
| Token | Value | Tailwind | For |
|---|---|---|---|
--duration-stagger | 40ms | duration-stagger | The offset between items entering one after another. |
--duration-micro | 80ms | duration-micro | A tooltip's intent delay, a shake segment, a stagger of few large items. |
--duration-quick | 150ms | duration-quick | Closing a dropdown or dialog, swapping text, a tooltip appearing. A bare transition. |
--duration-fast | 250ms | duration-fast | Opening a dropdown or dialog, swapping an icon, sliding tabs or pages. |
--duration-medium | 350ms | duration-medium | Closing a panel, a toast leaving. |
--duration-slow | 400ms | duration-slow | Opening a panel, revealing loaded content, clearing an input. |
--duration-very-slow | 500ms | duration-very-slow | Emphasis: a badge appearing, a text reveal, a success check. |
Easings
Tailwind's ease-* scale. Smooth-out is the default; the others each carry their own intent. Played here on duration-very-slow.
ease-smooth-outease-in-outease-outease-linearease-bounceease-bounce-strong
| Token | Value | Tailwind | For |
|---|---|---|---|
--ease-smooth-out | cubic-bezier(0.22, 1, 0.36, 1) | ease-smooth-out | The default. Surfaces opening and closing, slides, resizes, anything changing position. |
--ease-in-out | ease-in-out | ease-in-out | Swaps in place: icons, text, a reveal, a skeleton giving way. |
--ease-out | ease-out | ease-out | Tooltips, in and out. |
--ease-linear | linear | ease-linear | Loops that never settle: a shimmer, a pulse, a spinner. |
--ease-bounce | cubic-bezier(0.34, 1.36, 0.64, 1) | ease-bounce | A small pop on entering, like a badge. Never on a close. |
--ease-bounce-strong | cubic-bezier(0.34, 3.85, 0.64, 1) | ease-bounce-strong | A springy return when the pointer leaves a row it lifted. |
Distance, scale and blur
Where a motion starts from; it always settles at rest. Read them as var(--…) in a transition or keyframes.
| Token | Value | For |
|---|---|---|
--distance-micro | 4px | Text swapping in place. |
--distance-small | 6px | An error shake's small segment. |
--distance-base | 8px | A page slide, a badge's reveal, an error shake's large segment. |
--distance-medium | 12px | Lines of text rising in. |
--distance-large | 30px | A one-off celebration, like a success badge. |
| Token | Value | For |
|---|---|---|
--scale-large | 0.96 | A dialog opening and closing. |
--scale-medium | 0.97 | A dropdown opening. |
--scale-small | 0.98 | A tooltip opening. |
--scale-tiny | 0.99 | A dropdown closing. |
| Token | Value | For |
|---|---|---|
--blur-small | 2px | A panel revealing, an icon or text swapping, a number popping in. |
--blur-medium | 3px | A page slide, text rising in. |
--blur-large | 8px | A success check arriving. |
Which motion, where
| Element | Motion | Timing |
|---|---|---|
| Input, textarea, code slot: focus | The ring settles in | quick · smooth-out |
| Button, chip: hover and press | Lift in, springier settle out | quick · smooth-out |
| Switch | Toggle: the thumb travels with a double bounce | medium · bounce |
| Sidebar items, select options, calendar days: hover | Glide: one wash travels to the nearest item, keys or mouse; appears in place first | quick · smooth-out · fades in place when motion is reduced |
| Sidebar: the current page | Glide: the current wash slides to the next page | fast · smooth-out, as tabs slide |
| Popover, select, menu | Menu dropdown: grows from its trigger | fast open · quick close · scale medium → tiny |
| Dialog | Modal: scales up, softer down | fast open · quick close · scale large |
| Sidebar, sheet, fullscreen menu | Panel reveal: slides in with a cross-blur | slow open · medium close · blur small |
| Toast | Toast: rises from below, slower in than out | medium · smooth-out · blur small |
| Field error | Error state shake | micro segments · distance small → base |
| Copy, send, any icon that changes | Icon swap: cross-fade with blur and scale | fast · in-out · blur small |
| Text that changes in place | Text states swap | quick · in-out · distance micro |
| Messages, headlines, step titles | Texts reveal: a staggered blurred rise | very slow · distance medium · stagger |
| Okami thinking | Thinking states: a shimmering status line | linear loop · quick swap |
| Booked, saved, done | Success check | very slow · blur large |
| A full-screen chat, a phone's keyboard coming and going | Keyboard: the page makes room as the keyboard slides, from the tap | fast · smooth-out · touch screens only |
| Days, steps, list ↔ detail | Page side-by-side | fast · distance base · blur medium |
The standard entrance
/* The texts reveal: animate-text-reveal (or .aura-rise on the aura) —
a 12px blurred rise on duration-very-slow, smooth-out. */
<h1 className="motion-safe:animate-text-reveal">…</h1>
/* stagger a load sequence in steps of --duration-micro */
<p className="motion-safe:animate-text-reveal animate-delay-micro">…</p>The landing's arrival
The landing's one orchestrated moment: the nav rises in piece by piece, then the headline sweeps in line by line, then the subhead, faster, then the way in — about 1.7s. The words and the way in run on one clock, held until the page runs, so nothing shows before the headline. Replay it here; the pieces are on BlurReveal.
Your craft, uninterrupted
Okami answers every message and call, books your clients, and brings them back — while you stay with the one in your chair.
| Element | Motion | Timing | Code |
|---|---|---|---|
| Nav: wordmark, links, CTA, menu | Each rises in (opacity, 6px), one after another, on load | very-slow · smooth-out, a micro step apart | blur-in, --blur-in-step |
| Hero headline | Letter by letter out of a 12px blur along each line, line after line | very-slow a letter, a line every medium | BlurReveal by="line" pace="duration-medium" |
| Hero subhead | The same, once the headline's two lines are in | a line every quick, after 2 × medium | BlurReveal pace="duration-quick" wait |
| Hero's way in | Rises in once the words are in, on their clock | very-slow · smooth-out, after 2 × medium + 3 × quick | RiseIn |
| Hero wash | The ball's colours laid flat, running into each other, drifting through the mixes; fades in over its own first frame | a mix every 5 × very-slow, in-out; the fade slow | GradientBall shape="field", HeroWash |
| Conversation cards | A new card rises from the foot, pushing the rest up; the oldest dissolve into the stack's eased top; a finished exchange fades | medium · smooth-out; the fade 700ms (paced to the exchange) | HeroStage, mask-stack |
| Craft row | Glides the craft on to the middle, round and round; the rest at 65% | very-slow · smooth-out; the fade quick | CraftRow (three copies) |
| Craft timer | The chosen tile's ring traces once round as its conversation runs | linear, the conversation's length | PeopleRow trace, trace-ring |
/* The nav: each piece a --duration-micro step after the last. */
<li className="blur-in" style={{ "--blur-in-step": 2 }}>…</li>
/* The hero: the headline, then the subhead, then the way in. */
<h1><BlurReveal by="line" pace="duration-medium">{headline}</BlurReveal></h1>
<p>
<BlurReveal by="line" pace="duration-quick" wait="calc(var(--duration-medium) * 2)">
{subhead}
</BlurReveal>
</p>
<RiseIn after="calc(var(--duration-medium) * 2 + var(--duration-quick) * 3)">
<ButtonLink href="#early-access">{cta}</ButtonLink>
</RiseIn>Waiting
- Waiting is shown by the thinking orb, not a spinner: Okami's dots bloomed into a slowly turning sphere of glass. Spinners are a last resort for the dashboard's true loading states.