Speed
Slow motion multiplier
1×
Reduced motion
Show

Semantic motion tokens

every control writes a real token
The current scale names sizes. An author choosing between --duration-fast-max and --duration-medium-min is guessing, because neither name says what it is for. The proposal keeps the primitive scale exactly as it is and adds a semantic layer on top, the way colour already works.Decide here: Whether six easings and eight durations are the right vocabulary, and what each value should be.
One curve is doing three jobs
Core ships exactly one easing token, --ease-standard: cubic-bezier(0.24, 1, 0.4, 1), and it covers entry, exit and state at once. The codebase has already discovered this and worked around it locally — with a comment measuring why. core/BottomSheet/BottomSheetPanel.tsx:172 authors cubic-bezier(0.3, 0, 0.6, 0.6) by hand for the sheet’s exit.

The same three jobs, one curve and six

run at 4× to judge
Watch the exit lane. On the standard curve a leaving surface spends its travel immediately and then coasts, which is right for an arrival and wrong for a departure.
Today — --ease-standard for all three
Arriving
Leaving
Moving
Proposed — the curve names the job
Arriving--ease-entry
Leaving--ease-exit
Moving--ease-move
The sheet needed its own curve because the standard one, measured on device, “put the sheet half off-screen in 59ms of a 410ms close and 90% off in 163ms — the travel is over before the eye has followed it”. That comment, already in the codebase, is the specification for --ease-exit. Promoting it is not a new idea; it is the second time someone has needed it.

Easing

Drag a control point and every demo in the lab moves with it. Arrow keys nudge; hold shift for a bigger step.

--ease-entry

Things arriving. Decelerates hard.
Easing curve cubic-bezier(0.23, 1, 0.32, 1)cubic-bezier(0.23, 1, 0.32, 1)
today — --ease-standard
proposed — --ease-entry
Within a hair of the current --ease-standard, so this is a rename with a nudge and almost nothing shifts.

--ease-exit

Things leaving. Accelerates away.
Easing curve cubic-bezier(0.3, 0, 0.6, 0.6)cubic-bezier(0.3, 0, 0.6, 0.6)
today — --ease-standard
proposed — --ease-exit
Not a proposal so much as a promotion: the sheet already authors this curve locally, with a comment measuring why the standard one would not do.Already in the code: BottomSheet/BottomSheetPanel.tsx:172

--ease-move

Movement on screen: sliding indicators, morphing surfaces, reorder.
Easing curve cubic-bezier(0.77, 0, 0.175, 1)cubic-bezier(0.77, 0, 0.175, 1)
today — --ease-standard
proposed — --ease-move
Symmetric in-out. The thing is already on screen, so it needs no arrival accent — only a legible path.

--ease-state

CSS keyword — nothing to tune
Hover, colour, selection.
today — --ease-standard
proposed — --ease-state
Symmetric and cheap. The sites that declare a duration and no curve are silently getting this already, so naming it changes nothing except that it becomes reviewable.Already in the code: DURATION_WITHOUT_CURVE in the audit

--ease-linear

CSS keyword — nothing to tune
Progress, spinners, marquees, scroll-linked motion.
today — --ease-standard
proposed — --ease-linear
Correct behaviour, wrong provenance: several sites hardcode it and most of them are right to. A token makes them lintable.Already in the code: Spinner, DateInput wheel, BottomSheet scrim, Stepper

--ease-drawer

Sheet and drawer travel.
Easing curve cubic-bezier(0.32, 0.72, 0, 1)cubic-bezier(0.32, 0.72, 0, 1)
today — --ease-standard
proposed — --ease-drawer
The iOS curve. Matches what BottomSheet and MobileNav are reaching for when they hand-tune sheet travel.

Duration

Aliases onto the existing primitives, so a theme retunes them for free through the motion scale it already has.

--duration-instant

new
Reduced motion and deliberate discrete flips.
Replaces the spellings in use today. The 0.01s form is the common one and it is not arbitrary — a zero duration fires no transitionend, so anything sequencing off that event breaks. Decide 0 vs 0.01ms before the sweep.

--duration-press

fast-min
Press and active feedback.
press
130ms
Inside the interface budget
Reference budget 100160ms.

--duration-state

fast
Hover, colour, selection, focus ring.
state
175ms
Inside the interface budget
Reference budget 100250ms.

--duration-enter

fast-max
Popovers, tooltips, menus arriving.
enter
230ms
Inside the interface budget
Reference budget 125250ms.

--duration-exit

fast
Dismissal.
exit
175ms
Inside the interface budget
Reference budget 100250ms.Deliberately shorter than enter, because old content should leave faster than new content arrives.

--duration-reveal

medium-min
Disclosure, collapse, expand.
reveal
310ms
Overlays only above 300ms
Reference budget 200350ms.

--duration-overlay

medium
Dialogs, drawers, sheets.
overlay
410ms
Overlays only above 300ms
Reference budget 200500ms.

--duration-continuous

slow
Spinners, progress, ambient loops.
continuous
900ms
Outside every interface budget
Does not reach the loops that actually exist: StatusDot runs 2s and Chat 1.5s, both outside a scale that stops at 1300ms.

Stagger

Group entrances want 30–80ms between items. Longer feels slow, and stagger must never block interaction — the last item has to be reachable before it has finished arriving.
--stagger-tightDense lists, small items.
tight
30ms
--stagger-baseDefault group entrance.
base
50ms
--stagger-looseFew large items. Longer than this feels slow.
loose
80ms

The primitive scale, unchanged

Theme-generated and working. Nothing here moves — the semantic layer sits on top, which is what keeps the change non-breaking.
PrimitiveValueSemantic alias
--duration-fast-min130ms
--duration-press
--duration-fast175ms
--duration-state--duration-exit
--duration-fast-max230ms
--duration-enter
--duration-medium-min310ms
--duration-reveal
--duration-medium410ms
--duration-overlay
--duration-medium-max550ms
--duration-slow-min730ms
--duration-slow975ms
--duration-continuous
--duration-slow-max1300ms

Where this lands

Not a :root block — core defines its tokens through StyleX, so the proposal is an addition to two existing objects.
@astryxdesign/core/src/theme/tokens.stylex.tsts
export const easeDefaults = {
'--ease-standard': 'cubic-bezier(0.24, 1, 0.4, 1)', // kept, deprecated in docs
'--ease-entry': "cubic-bezier(0.23, 1, 0.32, 1)",
'--ease-exit': "cubic-bezier(0.3, 0, 0.6, 0.6)",
'--ease-move': "cubic-bezier(0.77, 0, 0.175, 1)",
'--ease-state': "ease",
'--ease-linear': "linear",
'--ease-drawer': "cubic-bezier(0.32, 0.72, 0, 1)",
} as const;
export const durationDefaults = {
// the nine primitives stay exactly as they are
...
// semantic layer, aliased so a theme retunes both at once
'--duration-instant': "0ms",
'--duration-press': "130ms", // --duration-fast-min
'--duration-state': "175ms", // --duration-fast
'--duration-enter': "230ms", // --duration-fast-max
'--duration-exit': "175ms", // --duration-fast
'--duration-reveal': "310ms", // --duration-medium-min
'--duration-overlay': "410ms", // --duration-medium
'--duration-continuous': "975ms", // --duration-slow
} as const;
The export page emits this with whatever you have tuned. See Export tuning.