Motion
Motion is tokenised like color and type. Six durations, four curves, and nothing hand-typed in between — which is the whole point: a generated interface gives itself away withon everything, because that is what every generator reaches for by default.
Tune it
Every control below writes straight to the CSS variables on this document, so the components on this page — including the ones that render through portals — re-time as you drag. Nothing is saved; reload to get the shipped values back.
Duration
Six steps, no free numbers. Drag one and every component below re-times as you drag.
Easing
Drag either handle. The y axis runs past 1 on purpose — that is where overshoot lives.
ease-entranceAnything arriving. Fast start, long settle.
cubic-bezier(0.22, 1.00, 0.36, 1.00)ease-exitAnything leaving. Accelerates away.
cubic-bezier(0.40, 0.00, 1.00, 1.00)ease-moveRepositioning while staying on screen.
cubic-bezier(0.65, 0.00, 0.35, 1.00)ease-overshootDialog, popover, switch. Once per screen.
cubic-bezier(0.34, 1.35, 0.64, 1.00)These are the values WunderUI ships with. Change something and the CSS to paste appears here.
duration-fast · ease-entranceduration-fast in · duration-instant outduration-base · ease-overshoot in · ease-exit outduration-base in · duration-fast outduration-slow · ease-overshoot in · ease-exit outduration-deliberate in · duration-slow outduration-slow · ease-overshootduration-base · ease-entranceduration-ambient · ease-entranceUsing it in code
The duration steps are custom utilities, the easings are a Tailwind theme namespace. Both work with every variant, including the and states Base UI exposes.
<Dialog.Popup className=" data-open:animate-in data-open:fade-in-0 data-open:zoom-in-92 data-open:duration-slow data-open:ease-overshoot data-closed:animate-out data-closed:fade-out-0 data-closed:duration-fast data-closed:ease-exit " />
Because the easing tokens are declared with plain rather than , each utility emits instead of a baked-in curve. Redefine the variable on any wrapper and everything inside it takes on that feel — one marketing section can breathe more slowly than the app around it without forking a single component.
Travel & scale
How far a thing moves matters as much as how long it takes. These are deliberately small.
| Token | Value | Reach for it when |
|---|---|---|
| --travel-xs | 2px | Tooltip — stays glued to its anchor |
| --travel-sm | 4px | Popover, dropdown, context menu |
| --travel-md | 8px | Dialog, toast, inline panel |
| --travel-lg | 16px | Section reveal on scroll |
| --scale-press | 0.97 | Pressed button |
| --scale-enter | 0.96 | Dialog arriving |
Rules
- 1Duration and easing are class names, not numbers
duration-base, ease-entrance. Tailwind v4 has no --duration-* theme namespace, so WunderUI ships the scale as @utility rules that also set --tw-duration — which means they work on animate-in and animate-out, not just on transitions.
- 2Entrances decelerate, exits accelerate
ease-entrance in, ease-exit out, and the exit is one step shorter on the duration scale. Never the entrance duration played backwards: the user already decided, and making them watch the decision play out is the most common motion mistake there is.
- 3Only transform and opacity
Everything else goes through layout or paint. Height animates via grid-template-rows: 0fr → 1fr, never via height — that is the one trick that works in every browser and stays on the compositor.
- 4Restrain the travel
Anything anchored to a trigger moves 2px or 4px. A menu that slides 20px does not look like it opened; it looks like it fell in.
- 5One overshoot per screen
ease-overshoot is punctuation. Two of them in one view and the interface reads as a toy. A dialog overshoots; a sheet that fills half the viewport does not.
- 6Never transition-all
Name the properties, or use bare transition, which covers color, shadow, transform and opacity but no layout property. transition-all animates things you never meant to animate, including on first paint.
- 7Reduced motion is opt-in, not a kill switch
Wrap the animation in @media (prefers-reduced-motion: no-preference) so the still state is the default. Written the other way round, you only have to forget one selector for someone to get motion they asked not to have.
What your agent gets
Everything on this page is also on the machine-readable side. The motion scale, the curves, the rules above and a set of copy-paste recipes ship inside DESIGN.md and design.json, so an agent generating a component against WunderUI reaches for the same way it reaches for — and produces motion that matches the rest of the system instead of the default it was trained on.