# Motion

> Four durations, three easings, wired as Tailwind's defaults, and the mandatory reduced-motion guard.

URL: https://apogee.avaui.dev/docs/motion
Last updated: 2026-10-15

Four durations, three easings, wired as Tailwind's defaults, and the mandatory reduced-motion guard.

Last updated October 15, 2026

Motion is token-driven like everything else. Every animated component reads the tokens below and respects `prefers-reduced-motion`.

## Tokens

| Token | Value | Use |
| --- | --- | --- |
| `--duration-rapid` | 100ms | Hover fills, press feedback |
| `--duration-fast` | 150ms | Menus, toggles, tooltips |
| `--duration-medium` | 250ms | Panels, popovers, chevrons |
| `--duration-slow` | 400ms | Dialogs, expanding regions |
| `--ease-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Default: quick start, soft landing |
| `--ease-accelerate` | `cubic-bezier(0.4, 0, 1, 1)` | Leaving the screen |
| `--ease-decelerate` | `cubic-bezier(0, 0, 0.2, 1)` | Entering the screen |

Easings become utilities (`ease-standard`, `ease-accelerate`, `ease-decelerate`). Durations stay CSS variables: in scoped CSS use `transition: opacity var(--duration-fast) var(--ease-standard)`; in markup use `duration-[var(--duration-medium)]`.

## Defaults

Tailwind’s default transition duration and easing point at `--duration-fast` and `--ease-standard`, so a bare `transition-colors` is on-brand:

```astro
<div class="rounded-md border border-neutral bg-elevation-raised px-4 py-2 text-sm transition-colors hover:bg-primary hover:text-on-primary motion-reduce:transition-none">
Hover me
</div>
```

Reach for an explicit duration only when the default is wrong for the size of the move:

```astro
<div class="… transition-colors duration-[var(--duration-medium)] ease-decelerate …">Slower</div>
```

## Reduced motion

Every transition and animation carries a guard. In markup, `motion-reduce:transition-none`. In scoped CSS:

```css
@media (prefers-reduced-motion: reduce) {
  .apogee-thing {
    transition: none;
  }
}
```

Looping animations (Spinner, Progress, Marquee, Skeleton) keep their own durations; the scale above is for transitions. Under reduced motion they slow down or stop rather than disappear, so the state they communicate stays visible.

## Do / Don’t

-   **Do** use `ease-decelerate` for things that appear and `ease-accelerate` for things that leave.
-   **Don’t** write a literal `150ms` or a `cubic-bezier()` in a component.
-   **Don’t** animate layout properties (`height`, `top`) when `transform` or `::details-content` can do it.
