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

Last updated

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:

Hover me

Show code
<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:

Slower

Show code
<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:

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