Motion.
Enough movement to make sense.
Fast feedback for controls. A measured transition for overlays. Motion explains a change, then gets out of the way.
Loading motion examples…
Enough movement to make sense.
Fast feedback for controls. A measured transition for overlays. Motion explains a change, then gets out of the way.
Loading motion examples…
Motion works well here: it handles retargeting, presence and sequencing, while ordinary hover/focus feedback stays in CSS. The animation library lives behind an optional public entry point; importing a Button or the tokens does not load Motion.
Our middle ground keeps the existing 120ms feedback and 200ms entrance timing. Standard spatial springs settle without overshoot. Expressive spatial springs add a small, deliberate bounce; effects remain critically damped in both schemes. These are Dixy values, not a reproduction of native platform constants.
Change your mind mid-flight
Reduced motion: immediate changes. Target: start.
The preview follows your OS setting even when the reduced-motion preview is unchecked. Change targets rapidly to feel spring continuity. Switching to reduced motion snaps an active visual to its current target; controls remain usable. Reset restores the controls and clears example state.
import "@dixy-ui/react/styles.css";
import { Theme } from "@dixy-ui/react";
import { useState } from "react";
import {
Button,
Inline,
Stack,
Text,
Theme as MotionScope,
useMotionPreferences,
} from "@dixy-ui/react";
import { MotionProvider } from "@dixy-ui/react/motion";
import { AnimatePresence, domAnimation, LazyMotion } from "motion/react";
import * as m from "motion/react-m";
import {Use the real controls together. Buttons and menu highlights respond in 120ms; dialogs and menus enter in 200ms and exit in 120ms. Drawers enter in 280ms and use the same shorter exit, scaled by swipe release velocity. Tabs move their indicator in 200ms while panel content changes immediately.
The indicator moves. The content is available immediately.
Ready for your next change.
import "@dixy-ui/react/styles.css";
import { Theme } from "@dixy-ui/react";
import { useState } from "react";
import {
Button,
Dialog,
Drawer,
Field,
Inline,
Menu,
Stack,
Tabs,
Text,
TextField,
Theme as ComponentScope,| Component / phase | Shared recipe | Normal duration |
|---|---|---|
button.feedback | feedback | 120ms |
checkbox.feedback | feedback | 120ms |
checkbox.indicator | feedback | 120ms |
dialog.enter | enter | 200ms |
dialog.exit | exit | 120ms |
drawer.enter | navigation | 280ms |
drawer.exit | exit | 120ms |
menu.feedback | feedback | 120ms |
menu.enter | enter | 200ms |
menu.exit | exit | 120ms |
switch.feedback | feedback | 120ms |
switch.thumb | feedback | 120ms |
tabs.feedback | feedback | 120ms |
tabs.indicator | enter | 200ms |
radio.feedback | feedback | 120ms |
radio.indicator | feedback | 120ms |
toggle.feedback | feedback | 120ms |
select.feedback | feedback | 120ms |
select.enter | enter | 200ms |
select.exit | exit | 120ms |
disclosure.feedback | feedback | 120ms |
disclosure.reveal | reveal | 280ms |
field.feedback | feedback | 120ms |
avatar.enter | enter | 200ms |
floating.enter | enter | 200ms |
floating.exit | exit | 120ms |
range.feedback | feedback | 120ms |
toast.enter | enter | 200ms |
toast.exit | exit | 120ms |
scrollArea.feedback | feedback | 120ms |
These component aliases are generated from the public componentMotion map. For example, --dixy-menu-motion-enter combines its duration and easing aliases; customize those on Theme to carry them through portals. Focus, disabled states, action callbacks, keyboard navigation and Base UI presence remain independent of timing.
Reduced motion makes transitions immediate and removes decorative press, scale and entrance travel. It preserves the final tab-indicator location, drawer snap offset and direct pointer tracking. Swipe tracking has no interpolation lag in either mode. Progress uses a separate 700ms loop cadence and becomes static under reduction; it does not redefine the duration limits for ordinary UI feedback.
Three controls redistribute a small amount of space during a press. This is an opt-in documentation prototype: flex growth does layout work, so it stays outside the default component recipes. Selection remains independent of press feedback. Narrow containers keep equal widths, and reduced motion removes expansion.
Selected: Explore. Expansion applies only while pressing, when at least 352px is available.
import "@dixy-ui/react/styles.css";
import { Theme } from "@dixy-ui/react";
import { useState } from "react";
import { Text, Theme as GroupScope, Toggle, ToggleGroup } from "@dixy-ui/react";
interface ExpressiveGroupExampleProps {
enabled?: boolean;
reduceMotion?: boolean;
rtl?: boolean;
}
/** Docs-only bounded layout experiment. Selection is persistent; press expansion is momentary. */
function ExpressiveGroupExample({
enabled = false,
reduceMotion = false,
rtl = false,Stiffness pulls toward the target; mass resists acceleration; damping removes energy. A damping ratio of 1 settles without bounce. A ratio below 1 permits overshoot. Motion expects a damping coefficient, so convert the ratio using c = 2ζ√(km). Passing Material’s ratio directly as Motion’s damping would create a very different animation.
{
"type": "spring",
"stiffness": 500,
"damping": 44.721359549995796,
"mass": 1,
"restSpeed": 0.1,
"restDelta": 0.01
}A physical spring has a settling time rather than a fixed duration. We keep velocity under Motion’s control and do not mix stiffness/damping/mass with duration/bounce. Effects never overshoot opacity or color. The standard and expressive schemes share stiffness, isolating their difference to spatial damping.
| Scheme / kind | Speed | Stiffness | Ratio |
|---|---|---|---|
| standard / spatial | fast | 800 | 1 |
| standard / spatial | default | 500 | 1 |
| standard / spatial | slow | 280 | 1 |
| standard / effects | fast | 1600 | 1 |
| standard / effects | default | 900 | 1 |
| standard / effects | slow | 400 | 1 |
| expressive / spatial | fast | 800 | 0.8 |
| expressive / spatial | default | 500 | 0.8 |
| expressive / spatial | slow | 280 | 0.8 |
| expressive / effects | fast | 1600 | 1 |
| expressive / effects | default | 900 | 1 |
| expressive / effects | slow | 400 | 1 |
Keep ordinary UI animations under 300ms. Start at the lower end for frequent or small changes. Occasional page transitions are the explicit 300–400ms exception; never delay navigation or block input while waiting for them. These ranges are Dixy design guidance, not platform mandates.
| Element type | Duration | Dixy recipe |
|---|---|---|
| Micro-interactions | 100–150ms | feedback · 120ms |
| Standard UI · tooltips, dropdowns | 150–250ms | enter · 200ms |
| Modals, drawers | 200–300ms | enter · 200ms; navigation · 280ms |
| Page transitions · occasional | 300–400ms | page · 400ms |
Shorter exits can sit below the entrance range: Dialog exits use 120ms. Physical springs do not promise a fixed duration; inspect how quickly the content becomes usable and how long any residual movement lasts. Use a timing recipe when an exact deadline matters. Reduced motion takes priority over every range and becomes immediate.
Estimate how often a person will encounter the animation before choosing its expression.
The 100+ threshold is a design-review prompt, not a measured universal limit or a request to track people. Repetition is a reason to remove flourish. Never make completion depend on seeing an animation.
CSS durations use milliseconds; the Motion helpers convert them to seconds. Both read the same definitions. Recipes contain timing only, so each component explicitly lists the properties it animates. Avoid transition: all.
instant0msfast120msnormal200msdeliberate280msslow400ms0, 0, 1, 10.2, 0.8, 0.2, 10.16, 1, 0.3, 10.4, 0, 1, 1Timing comparison is immediate. Target: start.
| Role | Duration | Easing |
|---|---|---|
feedback | 120ms | standard |
enter | 200ms | enter |
exit | 120ms | exit |
navigation | 280ms | standard |
reveal | 280ms | enter |
page | 400ms | standard |
@import "@dixy-ui/tokens/motion.css";
.control {
transition: background-color var(--dixy-motion-transition-feedback);
}
.panel {
transition: opacity var(--dixy-motion-transition-enter),
transform var(--dixy-motion-transition-enter);
}Dialog keeps its existing Base UI lifecycle and starting/ending attributes. Motion powers the custom recipes above; it does not compete with the primitive’s mount/unmount handling. The spinner stays static when motion is reduced, while its accessible loading state remains. The loading preview starts only on request and stops automatically after four seconds.
Theme motion="system" respects the OS preference. motion="reduced" requests additional reduction and is inherited through nested themes and portals. A child cannot turn motion back on. The hook subscribes to live preference changes and renders static on the server until the client preference is known.
import { Theme, useMotionPreferences } from "@dixy-ui/react";
import { MotionProvider } from "@dixy-ui/react/motion";
import { springTransition } from "@dixy-ui/tokens";
// Install motion separately. Place the provider inside its Theme.
<Theme motion="system">
<MotionProvider>{/* Motion components */}</MotionProvider>
</Theme>;
// Inside a component:
const { reducedMotion } = useMotionPreferences();
const transition = springTransition("standard", "spatial", "default", reducedMotion);The optional provider sets MotionConfig’s reduction policy. Use the hook for opacity, color, sequencing delays, imperative animations and any loops too. Our recipes become immediate, sequence delays become zero, and no preview autoplays. There is no device or battery heuristic: reductions follow the person’s preference. Legacy --dixy-duration-fast, --dixy-duration-normal and --dixy-ease aliases remain available; prefer the explicit motion namespace for new work.
| Reference | Principle | Dixy decision |
|---|---|---|
| Material 3 Expressive ↗ | Interruptible springs; spatial and effects families, each with three speeds. | Keep the model. Use critically damped standard motion; reserve gentle overshoot for expressive spatial changes. |
| Material web specs ↗ | Spring-to-curve approximations are available for web animation without gestures or interruption. | Use real springs in Motion. Retain compact CSS timing recipes for controls and Base UI presence. |
| Radix animation ↗ | Primitive presence and exit lifecycles must be coordinated with the animation engine. | Keep Base UI responsible for Dialog presence and focus. Do not add a competing AnimatePresence lifecycle to it. |
| Apple motion ↗ | Purposeful, brief feedback should remain optional and cancellable. | No entrance on page load, autoplay, perpetual demo loops, or animation that blocks an action. |
| Motion accessibility ↗ | MotionConfig suppresses transform/layout motion; other effects need explicit policy. | Use one Theme preference, a live OS subscription, zero-duration recipes and explicit loop cancellation. |
Material’s Expressive fast spatial reference uses stiffness 800 and ratio 0.6; our expressive version uses the same stiffness with ratio 0.8 for less bounce. Material’s default spatial reference is 380/0.8; Dixy uses 500/0.8 for a firmer response. Effects use ratio 1. These deliberate web adjustments avoid pretending that one native platform’s numbers define every web context. Verified Material source values ↗