Tokens

Motion

Easing curves, durations and the shorthands that pair them. A component reads these rather than picking its own numbers, so retuning one value re-times everything that reads it.

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.

NameDescriptionTypeDefault
--wr-ease-linearNo easing. For continuous motion that should not accelerate — progress bars, spinners, marquees.linear
--wr-ease-outThe 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-inThe mirror of -out, for anything leaving. Slow start, fast exit.cubic-bezier(0.7, 0, 0.84, 0)
--wr-ease-in-outSymmetric. For motion that starts and ends on screen — a toggle sliding between two states.cubic-bezier(0.65, 0, 0.35, 1)
--wr-ease-springOvershoots 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-spring

Durations

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.

NameDescriptionTypeDefault
--wr-duration-fastHover / focus feedback. Fast enough to feel instant.0.1s
--wr-duration-baseThe default. State changes on controls — pressed, checked, selected.0.15s
--wr-duration-slowMotion that covers distance — a drawer sliding, a panel expanding.0.3s
--wr-duration-slowerDeliberate, 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.

NameDescriptionTypeDefault
--wr-transition-shortDuration + easing for hover / focus feedback.var(--wr-duration-fast) var(--wr-ease-out)
--wr-transition-baseDuration + easing for the common control state change.var(--wr-duration-base) var(--wr-ease-linear)
--wr-transition-longDuration + 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.

NameDescriptionTypeDefault
--wr-overlay-durationOpen / close timing for the anchored panels — dropdown, popover / tooltip, context menu, popconfirm — and the responsive bottom-sheet.var(--wr-duration-base)
--wr-overlay-easeEasing 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;
  }
}

See also