Motion

Motion is feedback. Every transition tells the user what happened, what's happening, or where to look. If it doesn't do one of those jobs, cut it.

The Oztix feel

Three principles that define how Oztix products move. Every animation decision should trace back to one of these.

Responsive

Motion reacts instantly to input. Like a crew backstage — things happen when they should.

Grounded

Elements feel like they have weight. Things land where they belong, with just enough give to feel real.

Efficient

Every animation earns its place. If it doesn’t orient, direct, or confirm — cut it.

Timing

Duration scales with distance and size. A toggle switch moves fast. A modal needs more time. Match duration to the physical scale of the element.

TokenValueTierWhen to use
--duration-instant0msMicroImmediate state changes, no visible transition
--duration-fastest50msMicroOpacity flashes, active press feedback
--duration-fast100msMicroTooltips, focus rings, subtle hover shifts
--duration-normal150msMicroButton hover, icon rotation, small transforms
--duration-moderate200msComponentColor transitions, border changes, emphasis shifts
--duration-slow300msComponentAccordion expand, dropdown open, card flip
--duration-slower400msPageModal enter, panel slide, route transition
--duration-slowest600msPageFull-page transitions, complex orchestrations

Rule of thumb: 50–150ms for micro-interactions (hover, focus, press). 200–300ms for component transitions (accordion, dropdown). 400–600ms for page transitions (modal, route change).

Duration comparison — hover each to see the speed

Fast

duration-fast

Normal

duration-normal

Moderate

duration-moderate

Slow

duration-slow

Feel

Easing defines the character of motion. Standard curves for color and opacity. Spring physics for transforms — the overshoot gives elements physical weight.

TokenCurveWhen to use
--ease-standardcubic-bezier(0.4, 0, 0.2, 1)Default for color, opacity, box-shadow transitions
--ease-entercubic-bezier(0, 0, 0.2, 1)Elements entering — starts fast, settles gently
--ease-exitcubic-bezier(0.4, 0, 1, 1)Elements leaving — starts slow, accelerates out
--ease-springlinear(...)Transforms only — slight overshoot for physical feel

Easing comparison — hover to see each curve in action

Standard

ease-standard

Symmetric — color & opacity

Enter

ease-enter

Decelerating — elements arriving

Exit

ease-exit

Accelerating — elements leaving

Spring

ease-spring

Overshoot — transforms only

Key rule: Use --ease-spring for transform properties (scale, translate, rotate). Use --ease-standard for background-color, opacity, and box-shadow. Spring physics on color changes has no physical analogue — it just looks wrong.

The toolkit

Pre-built animation utilities for common patterns. These compose with Tailwind's motion-safe: and motion-reduce: variants and are automatically neutralised by the global reduced motion reset.

UtilityWhat it doesUsage
motion-fade-inFade in from transparentmotion-fade-in
motion-fade-outFade out to transparentmotion-fade-out
motion-scale-inScale up from 95% with fade — popups, dropdownsmotion-scale-in
motion-scale-outScale down to 95% with fade — popup dismissmotion-scale-out

Motion across the system

Motion tokens don't live in isolation — they're wired into every interactive foundation. Here's where they connect.

Everyone gets in

Motion causes nausea for some users. This is a health issue, not a preference. Roadie includes a global prefers-reduced-motion reset that neutralises all transitions and animations automatically.

What it does

Sets all animation-duration and transition-duration to 0.01ms globally. Uses 0.01ms instead of 0ms so JavaScript transition and animation events still fire correctly.

How to test

In Chrome DevTools, open the Rendering tab and set “Emulate CSS media feature prefers-reduced-motion” to “reduce”. On macOS, go to System Settings > Accessibility > Display > Reduce motion.

Note: Tailwind's motion-safe: and motion-reduce: variants are still available for cases where you want to swap a motion for a static alternative rather than simply disabling it.

Guidelines

Rules for how motion should work across Oztix applications. Follow these when building custom animated UI.

Give every animation a job

Motion communicates cause and effect. If an animation doesn’t orient, direct focus, give feedback, or show a state change — cut it.

Do

A button press scales down to confirm the click landed. A select popup scaling in from its trigger shows what opened it. Each transition has a job — feedback, orientation, or state change.

Look at me!

Don’t

A heading bounces on page load because someone thought it looked fun. Random animations on static content rarely communicate anything meaningful.

Be specific about what moves

Precision prevents side effects. List exactly which properties transition.

/* Explicit and safe */
transition:
background-color var(--duration-moderate) var(--ease-standard),
transform var(--duration-normal) var(--ease-spring);

Do

List exact transition properties. Roadie's is-interactive handles this automatically for standard interactive elements.

/* Animates everything, including layout */
transition: all 150ms ease-out;

Don’t

Use transition-all — it'll animate padding, font-size, and anything else that changes, causing layout recalculations on every frame.

Honour the user’s preferences

Reduced motion is a health accommodation, not a design preference.

/* Roadie's global reset — automatic */
@media (prefers-reduced-motion: reduce) {
* { transition-duration: 0.01ms !important; }
}

Do

The global prefers-reduced-motion reset handles this automatically. Test with it enabled in DevTools before shipping.

Don’t

Ship animations without checking. Force parallax effects on users who have explicitly asked to minimise motion. Vestibular disorders make this a health issue.

Use tokens, not magic numbers

Tokens keep motion consistent as the system grows. Hardcoded values drift.

transition:
background-color var(--duration-moderate) var(--ease-standard);

Do

Use duration and easing tokens. One change updates every component.

transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);

Don’t

Hardcode 0.2s cubic-bezier(0.4, 0, 0.2, 1) across 15 files — one typo and things feel off.

Springs for transforms, easing for color

Spring physics create a sense of mass and momentum. That metaphor only works for spatial motion.

Spring on transform

Standard on color

Do

Use ease-spring for transform transitions — the slight overshoot gives elements physical weight. Use ease-standard for background-color and opacity.

Don’t

Apply spring easing to opacity or color — there's no physical analogue to spring tension on a colour change. It adds overhead with no perceptual benefit.

Never block the interface

Users are faster than animations. Never lock the UI behind a transition.

Do

Ensure animations can be cancelled or reversed immediately. A user clicking “Close” on an opening modal should trigger the close animation at once. Use CSS transitions (interruptible) over keyframe animations (fire-and-forget) where possible.

Don’t

Force the user to watch a 2-second success animation before they can click “Next”. Delightful once, infuriating on the 50th use.

Things grow from where they are

Physics requires a pivot. Elements should expand from their anchor point.

/* Dropdown scales from its trigger */
.popup {
transform-origin: var(--transform-origin);
transition: transform var(--duration-normal) var(--ease-spring);
}

Do

A dropdown menu scales from its trigger button. A modal expands from the button that opened it. Roadie's Select and Combobox popups handle this automatically via Base UI's --transform-origin.

Don’t

Scale a popup from the centre of the screen when it was triggered from a button in the bottom-right corner — this breaks the spatial illusion.

Stagger, don’t flood

Choreography creates hierarchy. Stagger related elements to guide the eye.

/* Stagger items using the token */
.item { animation-delay: calc(var(--stagger-base) * var(--i)); }

Do

When a list of items enters the screen, stagger each item by 30–50ms (--stagger-base) so the eye is led through the sequence rather than overwhelmed.

Don’t

Animate all 12 cards in a grid simultaneously — the result is a flash, not a reveal.

Quick reference

How motion tokens map to specific components. See Interactions for the full is-interactive and is-interactive-field behaviour breakdown.

WhatDurationEasingNotes
Button hover/pressduration-moderateease-standardHandled by is-interactive
Button transformduration-normalease-springscale(0.99) on active press
Input focus/hoverduration-moderateease-enterHandled by is-interactive-field
Focus ringduration-normalease-standard / ease-enterOutline width and colour
Caret rotationduration-moderateSelect and Combobox trigger icons
Popup enter/exitduration-normalease-springmotion-scale-in / motion-scale-out
Accordion open/closeduration-moderateease-enterCSS transition on height via --collapsible-panel-height
Shadow on hoverduration-moderateease-standardemphasis-raised lifts via Elevation