Motion
Duration and easing are separate tokens. Pick one of each. Patterns ship a default pair so the common case is already decided, and you can change speed or curve without a new token.
| Kind | Default duration | Default easing | Use for |
|---|---|---|---|
| Chrome | --af-duration-fast | --af-ease-standard | Hover, border, text, shadow |
| Movement | --af-duration-md | --af-ease-out | Drawer, accordion height, entering panels |
| Keyframes | --af-animation-duration-* | --af-ease-linear or --af-ease-in-out | Spinner, skeleton |
Swap either token when the default is wrong. A faster drawer is --af-duration-fast with the same --af-ease-out. A stronger hover is --af-ease-emphasized with the same --af-duration-fast. Do not write ms or cubic-bezier(...) in product CSS.
Two duration families:
| Family | For | Examples |
|---|---|---|
--af-duration-* | CSS transitions | Hover color, border, accordion height, drawer |
--af-animation-duration-* | CSS keyframe animations | Spinner, skeleton, entrance keyframes |
Duration
Section titled “Duration”Transitions
Section titled “Transitions”| Token | Default | Use for |
|---|---|---|
--af-duration-fast | 120ms | Hover, color, border, opacity, focus chrome |
--af-duration-md | 200ms | Default layout motion (panels, accordion) |
--af-duration-slow | 320ms | Longer panel / emphasis transitions |
Keyframes
Section titled “Keyframes”| Token | Default | Use for |
|---|---|---|
--af-animation-duration-fast | 200ms | Short micro-motion |
--af-animation-duration-md | 400ms | Default keyframe length |
--af-animation-duration-slow | 600ms | Spinners, short loops |
--af-animation-duration-slower | 1500ms | Skeleton shimmer, long loops |
Compare transitions
Section titled “Compare transitions”Hover each swatch. Same property change; different --af-duration-*.
<button class="demo-motion-swatch" style="transition-duration: var(--af-duration-fast)">Fast</button>
<button class="demo-motion-swatch" style="transition-duration: var(--af-duration-md)">Md</button>
<button class="demo-motion-swatch" style="transition-duration: var(--af-duration-slow)">Slow</button>Easing
Section titled “Easing”| Token | Default | Use for |
|---|---|---|
--af-ease-standard | cubic-bezier(0.2, 0, 0.2, 1) | Default UI transitions (buttons, fields) |
--af-ease-emphasized | cubic-bezier(0.2, 0, 0, 1) | Stronger decelerate / emphasis |
--af-ease-out | cubic-bezier(0, 0, 0.2, 1) | Entering panels, drawer slide, accordion |
--af-ease-in | cubic-bezier(0.4, 0, 1, 1) | Exiting / accelerating away |
--af-ease-in-out | cubic-bezier(0.4, 0, 0.2, 1) | Symmetric loops (skeleton) |
--af-ease-linear | linear | Progress, indeterminate spin |
Compare easing
Section titled “Compare easing”Same duration (--af-animation-duration-slower); each row uses a different --af-ease-*. Watch how the dots accelerate.
-
standard
-
emphasized
-
out
-
in
-
in-out
-
linear
animation: demo-ease-travel var(--af-animation-duration-slower) var(--af-ease-standard) infinite alternate;Usage in CSS
Section titled “Usage in CSS”Transitions (most pattern chrome):
.af-btn {
transition:
background-color var(--af-duration-fast) var(--af-ease-standard),
border-color var(--af-duration-fast) var(--af-ease-standard),
color var(--af-duration-fast) var(--af-ease-standard);
}Keyframes (Spinner, Skeleton):
.af-spinner {
animation: af-spin var(--af-animation-duration-slow) var(--af-ease-linear) infinite;
}
.af-skeleton {
animation: af-skeleton-load var(--af-animation-duration-slower) var(--af-ease-in-out) infinite;
}Layout motion (Drawer slide, Accordion):
transition:
transform var(--af-duration-md) var(--af-ease-out),
height var(--af-duration-md) var(--af-ease-out);Reduced motion
Section titled “Reduced motion”Airframe respects prefers-reduced-motion: reduce in two places:
- Tokens (
@airframeui/tokens) — every--af-duration-*and--af-animation-duration-*becomes0.01ms, including values you override in:root. - Base (
@airframeui/core) —transition-duration/animation-durationforced near zero on all elements;animation-iteration-count: 1;scroll-behavior: auto.
Override timing in :root when you need a different product feel — reduced-motion still collapses those overrides:
:root {
--af-duration-fast: 80ms;
--af-duration-md: 160ms;
--af-animation-duration-slower: 2s;
}Force reduced motion in a subtree (previews, demos, dense admin chrome) with af-reduce-motion:
Default
af-reduce-motion
<div class="af-reduce-motion">
<span class="af-spinner" aria-hidden="true"></span>
</div>Demos on this page: If your OS prefers reduced motion, the hover comparisons above will snap. Turn the preference off (or use a browser without it) to feel the curves.
- Two tokens. A transition sets one
--af-duration-*and one--af-ease-*. Change either. No hardcodedmsorcubic-bezieroutside the token file.validate_markuprejects raw timing. - Start from the default pair. Chrome is
fast+standard. Movement ismd+out. Keyframes use--af-animation-duration-*. - Do not add a shorthand. Duration and easing stay separate so a product can retune speed without retuning the curve.
- Do not disable reduced motion. Do not set long durations under
@media (prefers-reduced-motion: reduce). The token collapse and base rules are the contract.
Related
Section titled “Related”- Tokens — motion primitives in the catalog
- Tokens full list — defaults per mode
- Reduce motion —
af-reduce-motion - Spinner
- Skeleton
- Drawer
- Accordion
- Switch
- Button