Motion
What moves in the kit, read from the code: thirteen --animate-* tokens, what moves outside them, the transitions, the curves, three staggers, and what switches off with reduced motion.
Tokens
Thirteen tokens in seven kinds (the Sheet has one pair per side). Replay each one; the line under it is where the kit uses it today.
Something appearing in place, with no distance to travel: the scrim behind an overlay, an accordion or collapsible panel, a navigation menu panel, the shell on first paint.
Used by Sheet, Dialog and AlertDialog scrims · Accordion · Collapsible · NavigationMenu · Sidebar · TopBar
The Sheet scrim leaving. It is the only fade with an exit: Dialog and AlertDialog scrims close at once.
Used by Sheet scrim
A floating surface opened from a trigger: a dialog, a popover, a menu, a select list, a hover card. Rises 8px and grows from 97%. Closes at once, with no exit.
Used by Dialog · AlertDialog · Popover · DropdownMenu · ContextMenu · Menubar · HoverCard · Select · AccountMenu, and what is built on them (Combobox, DatePicker, Command…)
A page's own content arriving on load: the header, then each section. Rises 10px.
Used by PageHeader · the sections of the Base and Insights templates
The sidebar on first paint: every row slides 14px in from the left, one after another.
Used by Sidebar
A Sheet sliding in from the edge it is anchored to. The Sheet picks the token from its side prop.
Used by Sheet (one token per side: -right, -left, -top, -bottom)
The Sheet leaving through the same edge, faster and on the exit curve.
Used by Sheet (one token per side: -right, -left, -top, -bottom)
Outside the tokens
Eight things move, or don't, on timing that is not a kit token: two Tailwind loops, three libraries that bring their own motion, the agent orb and the tour with their own, and Tooltip with none. They are listed so nobody copies them as the rule.
| What | Value | Why |
|---|---|---|
animate-pulse Skeleton · the InputOTP caret | 2s loop · Tailwind | Tailwind's own loop. Says this is loading, or type here, and runs for as long as that lasts. |
animate-spin the loading toast · the Button loading recipe | 1s loop · Tailwind | Tailwind's own loop, on a loading icon: an action in progress. |
Toast (Sonner) Toast | 400ms · ease · Sonner | Sonner slides toasts in and out with its own transitions, and switches them off with reduced motion by its own rule. |
Chart (Recharts) every Recharts chart, unless it passes isAnimationActive={false} (DonutCard, several Insights template charts) | Line, Area, Pie 1500ms · Bar 400ms · Recharts | Recharts draws each series in when the chart mounts. Its default skips that with reduced motion. |
Drawer (vaul) Drawer, panel and scrim | 500ms · cubic-bezier(0.32, 0.72, 0, 1) · vaul | The vaul library drives it so the panel can follow a finger when dragged. Its duration and curve are vaul's; vaul ignores reduced motion, so the kit's Drawer switches the panel and its scrim off itself. |
AgentOrb AgentOrb | halo 2.8s and 1.4s · breath 2.4s · ring 2.4s · its own curves · WebGL frame loop | The orb is a character, not a surface: each state has its own beat, tuned by hand, and the ball runs a frame loop in WebGL. With reduced motion nothing moves: the ball shows each state in one frame and the halo or ring holds still where it carries the state. |
Tour Tour: its cut-out, its card and its demo cursor | cut-out: a critically damped spring, about 350ms, no overshoot; its fill 260ms · card: fades in 300ms once the light has landed · cursor 420 to 1000ms by distance on cubic-bezier(0.22, 1, 0.36, 1) · click pulse: Tailwind ping, once · close: 300ms fade | A tour moves things across the screen between targets it does not know in advance, so a fixed token duration does not fit: a short hop and a trip across the page need different times. The cut-out moves on a spring because its target is measured again while it moves (a page scrolling, an answer growing): a transition restarts its curve every time and the light arrives in slowing steps. The cursor borrows the kit's landing curve. With reduced motion the cut-out and the card jump, and the cursor's demo does not play at all. |
Tooltip Tooltip | none | Appears and disappears at once. There is no animation to match. |
Transitions
A transition eases a change of state (a hover, a switch, a caret turning, a width) instead of playing an entrance. Unless a class names a duration, it lasts Tailwind's 150ms.
| What | Value | Used by |
|---|---|---|
| Hover and focus: colour, background, border, shadow, opacity. | 150ms | transition-colors, transition-opacity, transition-shadow, transition-all (cards that lift their shadow, Tabs and Accordion triggers, InputOTP slots) |
| A control changing state or reacting to hover. | 150ms | the Switch thumb · the Progress fill · the Slider thumb shadow · ShortcutTile growing to 105% |
| A caret turning when its section opens or closes: always duration-200. | 200ms | Accordion · NavigationMenu · Sidebar · AccountMenu · InfoBlock · SectionHeader · CheckoutSelect |
| The sidebar collapsing to icons and back. | 200ms · ease-out | Sidebar |
Curves
Token entrances run 240 to 550ms and exits 200ms: an exit is always shorter than the entrance it undoes.
| Curve | Value | Use for |
|---|---|---|
| Enter | cubic-bezier(0.22, 1, 0.36, 1) | Starts fast and settles softly. Every token entrance except the fades. |
| Exit | cubic-bezier(0.55, 0, 1, 0.45) | Accelerates out of view. The Sheet exits. |
| Fade | ease-out / ease-in | Opacity only: ease-out on the way in, ease-in on the way out. |
| Transition | cubic-bezier(0.4, 0, 0.2, 1) | Tailwind's default, for every transition except the Sidebar collapse, which uses ease-out. |
Stagger
Three places bring their pieces in one after another. Each has its own rhythm, set by how many pieces it has: a few sections, many sidebar rows, the fields of a sheet.
| What | Timing | Animation |
|---|---|---|
| Page: the header, then each section of a template. | 100ms, then every 150ms: 250 · 400 · 550 | animate-cascade |
| Sidebar: every slot in reading order (labels, separators, items); sub-items when their parent opens. | 120ms + 30ms per slot · sub-items 30ms each | animate-slide-in-left |
| SheetBody: each direct child, starting while the sheet finishes landing (it lands at 300ms). | 220ms + 40ms per child · stops at the 10th (580ms) | cascade-in · 500ms |
The SheetBody stagger, running on the real rule:
Reduced motion
Usage
- Use the token that matches the job: dialog-in for a floating surface opened from a trigger, fade-in for scrims and panels that appear in place, sheet-* for sheets.
- Keep exits shorter than entrances.
- To bring pieces in one after another, reuse one of the kit's rhythms: 150ms between page sections, 30 to 40ms between rows.
- Test with reduced motion on (in DevTools, the Rendering panel, prefers-reduced-motion).
- Don't write a keyframe, a duration or a curve of your own in a prototype; if the kit lacks one, propose a token.
- Don't use a bounce or an overshoot; no curve in the kit goes past its target.
- Don't stagger a table or a long list row by row; the SheetBody stagger stops at the tenth child. The sidebar is the exception: it is the shell, drawn once per load.
- Don't animate content that updates on its own: a new row, a refreshed number, a polled status.
- Don't borrow classes from animation libraries the kit doesn't install (animate-in, fade-in-0, zoom-in-95): they compile to nothing.