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