⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Motion

The motion tokens that govern every transition, animation, and micro-interaction

Design System / Motion
Download .md file
Open .md in new tab

Motion tokens define timing and rhythm, not intent. They are reused for page transitions, component animations, and micro-interactions depending on context.

The system is built in two layers, the same model as color and spacing: primitives (raw easings and durations, named on the same t-shirt scale as --space-* and --font-*) → semantic tokens (named by intent, e.g. --motion-page-open). Always use semantic tokens in components, never hardcode cubic-bezier() or millisecond values inline.

The philosophy: durations follow the same t-shirt scale as spacing and type. Easings use the standard CSS keyword names (in, out, in-out) with refined cubic-bezier values. Zero new vocabulary to memorise. If you already know how --space-m works, you already know how --duration-m works.


Easing Primitives

Three primitives named after the standard CSS easing keywords — the cubic-bezier values are our refined replacements for the browser defaults — plus one emphasized curve for decisive layout motion.

Token Value Use for
cubic-bezier(0.4, 0, 1, 1) Exits. Fast start, slow end. The element accelerates away.
cubic-bezier(0.16, 1, 0.3, 1) Entrances. Slow start, slow end with a long graceful tail. The element settles into place.
cubic-bezier(0.65, 0, 0.35, 1) Swaps and continuous motion. Symmetric. Reads as a single sweep.
cubic-bezier(0.86, 0, 0.07, 1) Large layout shifts that should feel decisive (panel collapse, sidebar toggle, stage transforms). Near-still start and end, fast middle.

ease-in

ease-out

ease-in-out

emphasized

transition-timing-function: var(--ease-out);

Duration Primitives

Six durations on the same t-shirt scale as --space-*, --font-*, and --radius-*. Extendable in either direction (2xs, 2xl, etc.) when a new use case demands it.

Token Value Use for
100ms Hovers, presses, tooltips appearing
200ms Small UI feedback (toasts, focus rings, button state)
400ms Element-level transitions (dropdowns, expanding rows, fade swaps)
600ms Surface-level transitions (drawers, modals, page swaps)
800ms Page-level openings (full-area entrances)
1200ms Hero motion, deliberate reveals, intro animations
1500ms Extended reveals, complex staggered sequences
1800ms Long-form entrance animations
2000ms Full-page orchestrated transitions
2500ms Cinematic sequences
3000ms Slow ambient motion, background loops
30ms Per-item delay step for sequenced groups (list items, grid cards). A delay increment, not an animation duration.

2xs · 100ms

xs · 200ms

s · 400ms

m · 600ms

l · 800ms

xl · 1200ms

transition-duration: var(--duration-m);

Semantic Motion Tokens

Named by intent. These compose primitives. Components and product code only ever read the semantic layer, never the primitives directly.

In v1 only page-level semantic tokens exist, because that's the first consumer (studio's Barba page transitions). Element-, surface-, and feedback-level tokens will be added when a real component needs them.

Semantic token Composes Used for
+ A page rising into view (slide-up). 800ms with a long graceful tail.
+ A page falling away (slide-down). 600ms. Slightly faster than open, because the user has already decided to leave.
+ Sibling-page navigation (the conveyor). Single continuous sweep.
+ Crossfade fallback when no direction is known.

Each semantic token is stored as two CSS variables (one for duration, one for easing) so consumers can plug them into either CSS shorthand (transition) or JS animation APIs (element.animate(keyframes, { duration, easing })) without parsing.

/* The four page tokens, as declared in design-system.css */
--motion-page-open-duration:  var(--duration-l);
--motion-page-open-easing:    var(--ease-out);

--motion-page-close-duration: var(--duration-m);
--motion-page-close-easing:   var(--ease-in-out);

--motion-page-swap-duration:  var(--duration-m);
--motion-page-swap-easing:    var(--ease-in-out);

--motion-page-fade-duration:  var(--duration-s);
--motion-page-fade-easing:    var(--ease-in-out);

Naming Convention

When a new consumer needs a new semantic motion token, follow this pattern:

--motion-{scope}-{event}-{property}
Slot Allowed values
scope page · surface · element · feedback
event open · close · swap · enter · exit · fade · hover · press
property duration · easing

Examples (not yet defined, to be added when first needed):

  • --motion-surface-open-duration: drawers, modals, the contact overlay
  • --motion-element-hover-duration: buttons, links, hover states
  • --motion-feedback-toast-duration: toast notifications appearing

The semantic name should describe what kind of motion event this is, not which component triggers it. A token called --motion-button-hover would be wrong. Many things hover, not just buttons. --motion-element-hover is right.


Usage in CSS

Reference the duration and easing variables separately, the same way transition-duration and transition-timing-function already split in standard CSS:

.button {
  transition-property: background, color, border-color;
  transition-duration: var(--duration-2xs);
  transition-timing-function: var(--ease-out);
}

.dialog {
  transition-property: opacity, transform;
  transition-duration: var(--motion-page-fade-duration);
  transition-timing-function: var(--motion-page-fade-easing);
}

For component code, prefer the semantic token (--motion-*) when one exists. Fall back to the primitives (--duration-*, --ease-*) for genuinely new motion events that don't have a semantic name yet, but consider whether a new semantic token should be added first.


Usage in JavaScript

Read the tokens once at module load via getComputedStyle, then use them with the Web Animations API:

function readToken(name) {
  return getComputedStyle(document.documentElement)
    .getPropertyValue(name).trim();
}

const MOTION = {
  pageOpen: {
    duration: parseInt(readToken("--motion-page-open-duration"), 10),
    easing:   readToken("--motion-page-open-easing"),
  },
};

element.animate(
  [{ opacity: 0 }, { opacity: 1 }],
  { duration: MOTION.pageOpen.duration, easing: MOTION.pageOpen.easing }
);

This is the pattern used by studio/assets/js/studio-barba.js for page transitions.


Reduced Motion

The system does not define a separate set of tokens for prefers-reduced-motion users. Instead, individual consumers should check the user preference at animation time and either skip the animation or use --duration-2xs (effectively instant):

const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const duration = reducedMotion ? 0 : MOTION.pageOpen.duration;

This keeps the token surface small. If a future component needs more nuanced reduced-motion behaviour, add a --motion-{event}-reduced-duration variant alongside it.

On this page
  • Easing Primitives
  • Duration Primitives
  • Semantic Motion Tokens
  • Naming Convention
  • Usage in CSS
  • Usage in JavaScript
  • Reduced Motion
Previous

Icon

Next

Layout

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default