# 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.

Source: https://ngwr.dev/guides/tokens/motion  
Kind: Tokens

## 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)` | — |

## 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.

| 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)` | — |

```scss
/* 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.

```scss
/* 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.

```scss
/* 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

- [Theming](https://ngwr.dev/guides/theming) — Retuning these tokens, and every other one, across the app.
- [Animations](https://ngwr.dev/animations) — The ready-made animated components that build on these curves.
- [Overlay](https://ngwr.dev/guides/overlay) — The panel layer whose timing these tokens drive.
