Easing curves
Pick by direction, not by taste: -out for entering, -in for leaving, -in-out for motion that stays on screen, -linear for anything continuous.
| Name | Description | Type | Default |
|---|---|---|---|
--wr-ease-linear | No easing. For continuous motion that should not accelerate — progress bars, spinners, marquees. | linear | — |
--wr-ease-out | The default for anything entering. Fast start, long settle — the element arrives quickly then eases into place. | cubic-bezier(0.16, 1, 0.3, 1) | — |
--wr-ease-in | The mirror of -out, for anything leaving. Slow start, fast exit. | cubic-bezier(0.7, 0, 0.84, 0) | — |
--wr-ease-in-out | Symmetric. For motion that starts and ends on screen — a toggle sliding between two states. | cubic-bezier(0.65, 0, 0.35, 1) | — |
--wr-ease-spring | Overshoots past the target and settles back. Use sparingly — for playful, attention-drawing motion. | cubic-bezier(0.34, 1.56, 0.64, 1) | — |
--wr-ease-linear--wr-ease-out--wr-ease-in--wr-ease-in-out--wr-ease-springDurations
Four steps. The rule of thumb: the more distance an element covers, the longer it may take — feedback should feel instant, travel should be readable.
| Name | Description | Type | Default |
|---|---|---|---|
--wr-duration-fast | Hover / focus feedback. Fast enough to feel instant. | 0.1s | — |
--wr-duration-base | The default. State changes on controls — pressed, checked, selected. | 0.15s | — |
--wr-duration-slow | Motion that covers distance — a drawer sliding, a panel expanding. | 0.3s | — |
--wr-duration-slower | Deliberate, full-screen motion. Rare. | 0.5s | — |
Transition shorthands
Each pairs a duration with an easing. They deliberately omit the property — you name that at the call site, which is what lets one token serve background-color, transform and opacity alike.
| Name | Description | Type | Default |
|---|---|---|---|
--wr-transition-short | Duration + easing for hover / focus feedback. | var(--wr-duration-fast) var(--wr-ease-out) | — |
--wr-transition-base | Duration + easing for the common control state change. | var(--wr-duration-base) var(--wr-ease-linear) | — |
--wr-transition-long | Duration + easing for motion that travels. | var(--wr-duration-slow) var(--wr-ease-out) | — |
/* The shorthands carry duration + easing — NOT the property.
Name the property yourself, then hand it the token. */
.my-control {
transition: background-color var(--wr-transition-base),
box-shadow var(--wr-transition-short);
}
/* Or compose the parts when you need a one-off pairing: */
.my-drawer {
transition: transform var(--wr-duration-slow) var(--wr-ease-out);
}Overlay timing
The anchored panels — dropdown, popover / tooltip, context menu, popconfirm — plus the responsive bottom-sheet route their open animation through this one pair instead of their own timing, so retuning it moves all of them at once. Dialog, drawer and toast still carry a hardcoded duration of their own; overriding the pair does not reach them.
| Name | Description | Type | Default |
|---|---|---|---|
--wr-overlay-duration | Open / close timing for the anchored panels — dropdown, popover / tooltip, context menu, popconfirm — and the responsive bottom-sheet. | var(--wr-duration-base) | — |
--wr-overlay-ease | Easing for the same set. Retune both here and that layer follows together. | var(--wr-ease-out) | — |
Retuning the feel
Motion is a palette you can rebrand the same way as colour — override the tokens on :root and everything that reads them follows.
/* Retune globally — every component that reads the tokens follows.
Halving the durations makes the whole UI feel snappier at a stroke. */
:root {
--wr-duration-fast: 0.05s;
--wr-duration-base: 0.08s;
--wr-duration-slow: 0.15s;
}
/* Overlays share one timing hook, so the whole layer retunes together: */
:root {
--wr-overlay-duration: var(--wr-duration-slow);
--wr-overlay-ease: var(--wr-ease-spring);
}Reduced motion
Three layers cover it. The .wr-animate-* utilities drop to animation: none; the components whose motion is decorative or looping — marquee, typewriter, the text effects, burger, markdown's streaming caret, plus popover / context-menu / command-palette — opt out for themselves; and the theme layer gates the always-on chrome nobody opted into, which is where every overlay enter animation is switched off and the spinner is slowed rather than frozen. What is left is deliberate: CSS *transitions* — hover, focus and state changes across the catalog — are untouched, and so is the toast progress bar, which is a countdown. If your app treats the preference as absolute, kill the durations at the token layer, which is one rule for everything that reads them.
/* The .wr-animate-* utilities and the decorative components opt out for
themselves. For everything that reads the tokens, the durations ARE the
switch — one rule covers your styles and theirs together. */
@media (prefers-reduced-motion: reduce) {
:root {
--wr-duration-fast: 0.01ms;
--wr-duration-base: 0.01ms;
--wr-duration-slow: 0.01ms;
--wr-duration-slower: 0.01ms;
--wr-overlay-duration: 0.01ms;
}
.my-drawer {
transition-duration: 0.01ms;
}
}