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.
| Token | Value | Tier | When to use |
|---|---|---|---|
--duration-instant | 0ms | Micro | Immediate state changes, no visible transition |
--duration-fastest | 50ms | Micro | Opacity flashes, active press feedback |
--duration-fast | 100ms | Micro | Tooltips, focus rings, subtle hover shifts |
--duration-normal | 150ms | Micro | Button hover, icon rotation, small transforms |
--duration-moderate | 200ms | Component | Color transitions, border changes, emphasis shifts |
--duration-slow | 300ms | Component | Accordion expand, dropdown open, card flip |
--duration-slower | 400ms | Page | Modal enter, panel slide, route transition |
--duration-slowest | 600ms | Page | Full-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.
| Token | Curve | When to use |
|---|---|---|
--ease-standard | cubic-bezier(0.4, 0, 0.2, 1) | Default for color, opacity, box-shadow transitions |
--ease-enter | cubic-bezier(0, 0, 0.2, 1) | Elements entering — starts fast, settles gently |
--ease-exit | cubic-bezier(0.4, 0, 1, 1) | Elements leaving — starts slow, accelerates out |
--ease-spring | linear(...) | 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.
| Utility | What it does | Usage |
|---|---|---|
motion-fade-in | Fade in from transparent | motion-fade-in |
motion-fade-out | Fade out to transparent | motion-fade-out |
motion-scale-in | Scale up from 95% with fade — popups, dropdowns | motion-scale-in |
motion-scale-out | Scale down to 95% with fade — popup dismiss | motion-scale-out |
motion-pop-in | Fade + slide-up + scale entrance — pair with origin-* | motion-pop-in |
animate-nudge | One-shot vertical nudge — attention cue (cart bump) | animate-nudge |
animate-pop | One-shot scale pop — attention cue (badge update) | animate-pop |
Hover a tile to replay it
animate-nudge
Hover to play
animate-pop
Hover to play
motion-pop-in
Hover to play
Motion across the system
Motion tokens don't live in isolation — they're wired into every interactive foundation. Here's where they connect.
Interactions
is-interactive and is-interactive-field embed motion tokens directly. Button hover uses duration-moderate + ease-standard. Active press uses duration-normal + ease-spring. Field focus transitions use ease-enter.
Elevation
Shadow transitions on hover states are driven by motion tokens. When emphasis-raised lifts on hover, the shadow scales from shadow-md to shadow-lg using duration-moderate. Intent-tinted shadows shift color with the same easing.
Shape
Border-radius tiers affect how motion is perceived. Popups use rounded-xl and scale from transform-origin: var(--transform-origin) so they expand from their anchor. The radius ensures the scale animation reads as a smooth reveal, not a sharp resize.
Colors
Motion inherits color context via the intent cascade. A button inside intent-danger transitions to danger hover colors using the same tokens. Focus rings shift to the intent's accent color — the timing stays consistent.
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
Look at me!
Don’t
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
is-interactive handles this automatically for standard interactive elements./* Animates everything, including layout */transition: all 150ms ease-out;
Don’t
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
prefers-reduced-motion reset handles this automatically. Test with it enabled in DevTools before shipping.Don’t
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
transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
Don’t
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
ease-spring for transform transitions — the slight overshoot gives elements physical weight. Use ease-standard for background-color and opacity.Don’t
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
Don’t
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
--transform-origin.Don’t
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
--stagger-base) so the eye is led through the sequence rather than overwhelmed.Don’t
Quick reference
How motion tokens map to specific components. See Interactions for the full is-interactive and is-interactive-field behaviour breakdown.
| What | Duration | Easing | Notes |
|---|---|---|---|
| Button hover/press | duration-moderate | ease-standard | Handled by is-interactive |
| Button transform | duration-normal | ease-spring | scale(0.99) on active press |
| Input focus/hover | duration-moderate | ease-enter | Handled by is-interactive-field |
| Focus ring | duration-normal | ease-standard / ease-enter | Outline width and colour |
| Caret rotation | duration-moderate | — | Select and Combobox trigger icons |
| Popup enter/exit | duration-normal | ease-spring | motion-scale-in / motion-scale-out |
| Accordion open/close | duration-moderate | ease-enter | CSS transition on height via --collapsible-panel-height |
| Shadow on hover | duration-moderate | ease-standard | emphasis-raised lifts via Elevation |