Animation

Motion should make an interface easier to read: acknowledge an action, show where a change came from, or preserve context while the interface changes. If it does none of those things, leave it still.

Start with the state change, then add the smallest amount of motion needed to explain it. Motion must never delay input, hide information, or be the only way to understand a result.

The three jobs of motion

Confirm input

A press state, selected option, or saved toast confirms that the system received the action. Keep it immediate and short.

Connect states

Menus, panels, and reordered content should make their origin or destination legible, so people do not have to re-orient themselves.

Direct attention

Use motion sparingly for a meaningful new result. Do not animate every item competing for attention on a busy screen.

Decide before you animate

  1. What changed, and does the change need help being understood?
  2. Can the motion follow the user’s input or a visible source element?
  3. Will the final state be usable immediately for mouse, keyboard, and assistive-technology users?
  4. Is the same information clear when motion is reduced or absent?

Timing and easing

Interaction Duration Use it for
100–160ms Small scale, elevation, or color feedback while a control is pressed.
125–200ms A compact layer appearing near the control that opened it.
150–250ms A spatial relationship that should be noticed but not hold up selection.
200–300ms A larger context shift; keep the path short and the final focus ready.
--duration-100, --duration-120, --duration-160, --duration-180, --duration-300
Use these instead of one-off values.
--ease-out for entering or settling.
--ease-fade-out for a deliberate fade-out.

Prefer a quick ease-out curve for elements entering their resting state: var(--ease-out). It feels responsive because most of the distance is covered early. Use linear timing only when something is intentionally continuous, such as a progress indicator.

Motion tokens

Motion tokens are global design-system values. Use them directly in components rather than introducing local durations, duplicated curves, or fallback values. This keeps feedback consistent and lets one decision change the whole interface.

Token Value Use
100ms Very small selected-state or icon feedback.
120ms Press feedback on controls.
160ms Compact controls, icons, and content details.
180ms Default component transition and nearby layer motion.
300ms Larger context shifts such as drawers.
Token Curve Use
cubic-bezier(0.23, 1, 0.32, 1) Elements entering or settling into their final state.
cubic-bezier(0.4, 0, 0.8, 0.4) Intentional exits and fade-outs.

Use tokens in component motion

Choose the duration based on the size of the context change. Use the enter curve to settle a visible element; use the exit curve only when an element is leaving.

/* Entering or settling into place */ transition: transform var(--duration-180) var(--ease-out); /* A deliberate exit */ animation: fade-out var(--duration-180) var(--ease-fade-out) forwards;

Live patterns

Press feedback

Press the existing Button component. Its response should be immediate, subtle, and end exactly at its resting position; it is confirmation, not a loading indicator.

Origin-aware reveal

This uses the existing Popover and Card components. Its compact entry stays connected to the trigger, so new actions do not appear from nowhere.

Actions

Edit settings, duplicate item, or archive item.

Expand content in place

Use the Accordion component when content should remain connected to its heading. The control already provides focus behavior and an explicit expanded state.

Motion checklist
Keep the final state available immediately, preserve focus, and make the outcome understandable without relying on the transition.

Result, not spectacle

A Toast communicates a completed result in text first. If it enters, travel only a small distance and do not dismiss it before someone can read it.

Changes saved. Your preferences are up to date.

Implementation recipes

Enter a nearby layer

.c-popover { transform-origin: top center; animation: popover-enter var(--duration-180) var(--ease-out) both; } @keyframes popover-enter { from { opacity: 0; transform: translateY(-0.25rem) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }

Animate cheap properties

Prefer opacity and transform. Avoid animating layout dimensions, large blurs, and transition: all.

Match exit to intent

Exit motion can be faster than entry because the person initiated it. Do not make close actions wait for a long animation.

Do not create a fake delay

Do not delay navigation, focus, selection, or validation just to let motion finish. UI state changes immediately.

Reduced motion is the baseline fallback

Respect the operating-system preference. Keep the final state, focus management, and written feedback exactly the same; remove the travel and timing. Do not use motion as the only confirmation that something happened.

@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: 1ms !important; } }

Review checklist: motion has a purpose, finishes quickly, preserves focus and readable text, uses transform or opacity where possible, and remains understandable with reduced motion enabled.