Motion
Motion system
Every animation in Alea is one of 33 patterns, built only from the motion tokens. Motion explains what changed: where something came from, where the selection went, that a press was heard. With reduced motion, nothing travels; things still fade.
Change it, at three levels
Every component has a sensible default. You decide where motion goes and which one, without forking anything.
- The whole app:
ThemeProvider motionsets the mode (expressive, productive, reduced or off), follows or overrides the OS reduced motion setting, scales speed and intensity, and changes the default per component type. - One component type, in CSS:
.alea-popover { --alea-enter: alea-m-fade-in; --alea-exit: alea-m-fade-out; }. These variables don't inherit, so a dialog never leaks its motion into the select inside it. - One instance: every overlay takes a
motionprop: a preset,false, or{ enter, exit, duration, easing, delay }. See it live.
<ThemeProvider motion={{mode: 'productive', // expressive (default) | productive | reduced | offreduced: 'system', // follow the OS (default) | 'always' | 'never'speed: 0.9, // multiplies every durationintensity: 1.2, // multiplies distances, scales and blurcomponents: { dialog: 'scale', tooltip: 'fade', toast: { enter: 'slide-left', duration: 300 } },}}><Select motion="blur" /><DialogModal motion={{ enter: 'zoom', duration: 320, easing: 'spring' }} /><Popover motion={false} />
Presets: fade, scale, pop, rise, drop, slide-up, slide-down, slide-left, slide-right, blur, zoom, flip, morph, none. Each is a pair of keyframes, alea-m-<preset>-in and -out, that you can also use in your own CSS.
Modes
Try the same controls in each mode. Productive is shorter and quieter, for dense apps; reduced keeps fades and drops travel; off removes every animation.
Hover me
Patterns
The button at the bottom of the screen plays the automatic demos again; the rest you can try by hand.
Pop pop
Anchored overlays grow out of what opened them, from the side they open on, and shrink away faster than they came.
Rise rise
Page-level overlays come up a little while the backdrop fades. Toasts rise with a small overshoot and leave to the side.
Slide slide
Panels come in from their edge and go back the same way.
Stagger stagger
The items of a list that just opened arrive one after another, so the eye reads them top down. Only the first twelve wait.
Expand expand
Content opens to its own height, then fades in, so the layout below moves smoothly.
Glide glide
One indicator per group moves to the new item and takes its size, with a slight overshoot. You see where the selection went.
Hover me
It rises 4 px.
Lift lift
Surfaces you can click rise a little and cast a deeper shadow on hover.
Press press
Controls shrink slightly while pressed and spring back, so a tap feels answered.
Morph morph
An icon turns into its other state instead of swapping: chevrons rotate, checks pop in, the burger becomes a close icon, switches spring.
Tint tint
Hover and selection colors fade in very quickly; fast enough to feel instant, soft enough not to flicker.
Nudge nudge
A field that turns invalid shakes once, with the error text, so the problem is found without hunting.
Draw draw
Strokes and bars grow into place: progress, steps and chart lines.
Pulse pulse
Something live or new breathes slowly. It stops with reduced motion.
Shimmer shimmer
A sheen sweeps over placeholders while content loads.
Swap swap
Text or an icon changes with a short blur crossfade, so you notice the new word without a jump.
Roll roll
Digits roll to the new number, so a change in value reads as a change, not a flicker.
Badge pop badge
A count pops with a small spring when it changes (not on the first render).
Success success
The busy state turns into a check that draws itself, then goes back to the label.
Dissolve dissolve
Removed things blur, shrink and fade before they leave, so you see what went away.
Alea Motion animates what changed.
Resize resize
When content grows or shrinks, the box animates to its new height instead of jumping.
Fan fan
Overlapping items spread apart on hover or focus, so each one can be seen.
Ripple ripple
Press feedback grows from where you pressed (optional, off by default).
Hold hold
Holding fills a bar; the action runs only when it is full. Letting go cancels.
Hint hint
An arrow nudges toward where a link goes, on hover and keyboard focus.
Tokens
Components
Motion
Reveal reveal
Sections enter as they scroll into view, children one after another. Content that starts on screen, or without JavaScript, is never hidden.
See the bar at the very top of this page as you scroll.
Scroll scroll
A bar fills as you read; a header can hide while scrolling down and come back on the way up.
Thinking…
Stream stream
Streamed text fades in by chunk with a caret; a shimmer says the model is thinking.
Stack stack
Toasts pile up behind the newest one and fan out on hover or keyboard focus, so many notifications never cover the page.
Open a toast with the Stack demo and drag it sideways.
Swipe swipe
Drag a toast sideways to dismiss it: past 80 px or with a quick flick it leaves, otherwise it springs back. The close button stays for everyone else.
Loading loading
Something is working: a spinner turns, a bar sweeps when there is no percentage. They keep moving with reduced motion because they carry meaning; everything decorative stops.
Count count
A number counts up to its value when it first appears, so a stat reads as a result. Screen readers get the final number at once.
Ambient ambient
Slow, quiet movement in the background gives a surface life without asking for attention: orbs drift, a dot breathes, an aurora shifts. It stops completely with reduced motion.
Move between pages of these docs: they crossfade.
Route route
Page changes crossfade, slide or zoom with the View Transitions API (fade by default).
Easings
--alea-motion-ease-out--alea-motion-ease-in-out--alea-motion-ease-standard--alea-motion-ease-emphasized--alea-motion-ease-inDurations, distances and scales
--alea-motion-duration-instant120ms--alea-motion-duration-fast200ms--alea-motion-duration-base320ms--alea-motion-duration-slow480ms--alea-motion-duration-slower720ms--alea-motion-distance-sm8px--alea-motion-distance-md12px--alea-motion-distance-lg24px--alea-motion-scale-from0.92--alea-motion-scale-press0.96--alea-motion-stagger40ms--alea-motion-lift-4pxReduced motion
- Distances, scales, the lift and the stagger go to zero: overlays and items fade in place, nothing travels across the screen.
- Slow durations shorten to base; the emphasized curve stops overshooting.
- Decorative loops stop (pulse, orbs, the indicator ping); the skeleton shimmer slows to 3 s. Spinners keep turning because they tell you something is happening.
- Nothing important waits for an animation: exits finish under 200 ms, and focus moves at once.
Rules for new components
- Use a pattern; if none fits, add one here first. Never a one-off keyframe.
- Enter with ease-out, exit with ease-in and faster than the entrance.
- Fade from 0.01, not 0, so the first paint counts.
- Only animate transform and opacity (and height for expand): no layout jank.
- In Figma, the same patterns are interactive components with Smart Animate on the ease-out curve.