Skip to content

Motion grammar

UI motion follows four named durations and one default ease so transitions stay coherent. Bloom motion is documented separately in Surfaces / blooms.

Four durations

TokenValueUse
--motion-fast120mshover, focus fade-in
--motion-base220msbutton, input, pill — default
--motion-slow320mssheet, modal, panel
--motion-deliberate480mshero, marquee, dynamic-card stream-in

A fifth duration, --motion-instant (0ms), exists only to express "snap to end" — it is the same value reduced-motion collapses everything else to.

Default easing

--motion-ease-standard: cubic-bezier(.2, .7, .2, 1);

Use this curve unless you have a documented reason not to. The other named eases are listed below — each covers one specific directional case, and the set is closed.

Easings — full grammar

The canonical easings live in @matter/tokens as --motion-ease-*. Pick by the direction of the transition; the curve follows from the name.

TokenCurveWhen
--motion-ease-standardcubic-bezier(.2, .7, .2, 1)default — symmetric in / out, used for hover, focus, pill, button, input
--motion-ease-entrancecubic-bezier(0, 0, .2, 1)elements arriving on-screen — sheet open, toast slide-in, popover reveal
--motion-ease-exitcubic-bezier(.4, 0, 1, 1)elements leaving — sheet close, toast dismiss, popover hide
--motion-ease-emphasizecubic-bezier(.4, 0, .2, 1)continuous transitions that need a clear arc — tab indicator, segmented control
--motion-ease-linearlinearprogress, spinners, marquee — any motion that must not accelerate
--motion-ease-cubiccubic-bezier(.65, 0, .35, 1)symmetric ease-in-out for crossfades and tween-driven dynamic-card streams
--motion-ease-soft-bouncecubic-bezier(.34, 1.2, .64, 1)reserved for affirmative micro-moments — success check, capture flash
--motion-ease-snappycubic-bezier(.5, -.5, .25, 1.5)overshoot + settle — drag-release on a Kanban card, pill toggle commit
--motion-ease-anticipatecubic-bezier(.4, -.4, .25, 1)small pull-back before launch — used sparingly on confirm-style CTAs

Soft-bounce, snappy, and anticipate are opt-in — the standard, entrance, exit, emphasize, linear, and cubic set covers every default flow.

motion9 tokensv2.3.0
cubic-bezier(.2, .7, .2, 1)
matter
src ↗
cubic-bezier(0, 0, .2, 1)
matter
src ↗
cubic-bezier(.4, 0, 1, 1)
matter
src ↗
cubic-bezier(.4, 0, .2, 1)
matter
src ↗
linear
matter
src ↗
cubic-bezier(.65, 0, .35, 1)
matter
src ↗
cubic-bezier(.34, 1.2, .64, 1)
matter
src ↗
cubic-bezier(.5, -.5, .25, 1.5)
matter
src ↗
cubic-bezier(.4, -.4, .25, 1)
matter
src ↗

Reduced motion

@media (prefers-reduced-motion: reduce) collapses all four durations to 0ms and stops bloom keyframes. Single source of truth — the rule lives once in @matter/tokens/css/motion-recipes and every consuming app inherits it.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

The bloom orbs, the dynamic-card stream-in, the sheet overlay — all collapse to a static cross-fade under that media query.

Durations

instant
0ms
fast
120ms
base
220ms
slow
320ms
deliberate
480ms
duration.instant
0
duration.fast
180
duration.short
240
duration.base
220
duration.slow
320
duration.med
420
duration.deliberate
480
duration.long
520
duration.reveal
720
duration.glow
2500

Easings

easeStandard
cubic-bezier(.2, .7, .2, 1)
easeEntrance
cubic-bezier(0, 0, .2, 1)
easeExit
cubic-bezier(.4, 0, 1, 1)
easeEmphasize
cubic-bezier(.4, 0, .2, 1)
easeLinear
linear
easeCubic
cubic-bezier(.65, 0, .35, 1)
easeSoftBounce
cubic-bezier(.34, 1.2, .64, 1)
easeSnappy
cubic-bezier(.5, -.5, .25, 1.5)
easeAnticipate
cubic-bezier(.4, -.4, .25, 1)
easing.standard
cubic-bezier(0.2, 0.7, 0.2, 1)
easing.entrance
cubic-bezier(0, 0, 0.2, 1)
easing.exit
cubic-bezier(0.4, 0, 1, 1)
easing.emphasize
cubic-bezier(0.4, 0, 0.2, 1)
easing.linear
linear
easing.cubic
cubic-bezier(0.65, 0, 0.35, 1)
easing.softBounce
cubic-bezier(0.34, 1.2, 0.64, 1)
easing.snappy
cubic-bezier(0.5, -0.5, 0.25, 1.5)
easing.anticipate
cubic-bezier(0.4, -0.4, 0.25, 1)
easing.outSoft
cubic-bezier(0.22, 1, 0.36, 1)
easing.inOut
cubic-bezier(0.4, 0, 0.2, 1)

Brand layer — durations, staggers, brand easings

The brand layer in @repo/brand extends the canonical motion grammar with longer durations for marketing-surface choreography, staggers for sequenced reveals, and two brand easings that pair with --duration-glow and the painterly bloom keyframes. These live in packages/brand/src/tokens/motion.ts and ship as their own CSS vars so the marketing-surface motion stays severable from app-surface motion.

Durations

TokenValueUse
--duration-fast180msbrand-surface hover and focus tinting
--duration-short240msbrand-surface input and pill transitions
--duration-med420mssection reveal — the rhythm marketing pages cross-fade on
--duration-long520mshero choreography — title settles, eyebrow pulls in
--duration-reveal720msbrand-only "first paint" reveal on the landing canvas
--duration-glow2500mspainterly glow cycle on bloom art and signature backgrounds

--duration-glow is a cycle, not a one-shot transition — it pairs with --motion-ease-cubic on infinite-iteration keyframes only.

Staggers

TokenValueUse
--stagger-row80msper-row delay on Kanban / list stream-in
--stagger-line70msper-line delay on hero title and section eyebrow reveal

Use staggers as a multiplier on a sibling index — never as a duration. The companion duration is whichever the surface needs; the stagger only spaces out the start.

Brand easings

TokenCurveWhen
--ease-out-softcubic-bezier(0.22, 1, 0.36, 1)the brand-default deceleration — every --duration-med / --duration-long transition uses this
--ease-in-outcubic-bezier(0.4, 0, 0.2, 1)symmetric brand transitions — used for --duration-glow painterly cycles and bloom drift
motion23 tokensv2.3.0
0
brand
src ↗
180
brand
src ↗
240
brand
src ↗
220
brand
src ↗
320
brand
src ↗
420
brand
src ↗
480
brand
src ↗
520
brand
src ↗
720
brand
src ↗
2500
brand
src ↗
cubic-bezier(0.2, 0.7, 0.2, 1)
brand
src ↗
cubic-bezier(0, 0, 0.2, 1)
brand
src ↗
cubic-bezier(0.4, 0, 1, 1)
brand
src ↗
cubic-bezier(0.4, 0, 0.2, 1)
brand
src ↗
linear
brand
src ↗
cubic-bezier(0.65, 0, 0.35, 1)
brand
src ↗
cubic-bezier(0.34, 1.2, 0.64, 1)
brand
src ↗
cubic-bezier(0.5, -0.5, 0.25, 1.5)
brand
src ↗
cubic-bezier(0.4, -0.4, 0.25, 1)
brand
src ↗
cubic-bezier(0.22, 1, 0.36, 1)
brand
src ↗
cubic-bezier(0.4, 0, 0.2, 1)
brand
src ↗
80
brand
src ↗
70
brand
src ↗
Pick from the four app-surface durations. Use --motion-ease-standard unless the transition is directional. Reach for the brand-layer duration / stagger tokens on marketing surfaces only.
Don't author a one-off cubic-bezier() value. The canonical set covers every directional case; the brand set covers every choreography case.

On this page