Motion
Components share one set of durations, curves, and springs. Motion shows a change of state and obeys the reduced motion setting.
Principles
- Motion shows a change of state. It tells the user what changed and where it went. Do not add motion for decoration.
- Motion is interruptible. A new state starts from the current position. It does not wait for the last animation to finish.
- Move and scale with transforms. Fade with opacity. These run on the compositor. Color can change with a short transition. Only disclosure panels and LayoutMorph animate height.
- Motion obeys the reduced motion setting. The state change stays. The travel stops.
Tokens
The tokens are in motion.ts. The CLI adds this file with the first component that uses it. CSS uses Tailwind classes with the same values. The --ease-out-quint and --ease-spring theme tokens give the curves.
Durations
- duration.fast: 150 ms
- Hover, press, focus, and color changes. Exits from small overlays.duration-150
- duration.base: 200 ms
- Checks, indicators, and popover entrances. Most enter and exit animations.duration-200
- duration.slow: 250 ms
- Disclosure panels, chevrons, and height changes.duration-250
- duration.reveal: 450 ms
- Content that enters the view one time: TextReveal, StaggerGroup, and AnimatedNumber.
Curves
- easeOut: cubic-bezier(0.23, 1, 0.32, 1)
- The default curve for all tweens.ease-out-quint
- easeInOut: cubic-bezier(0.77, 0, 0.175, 1)
- Loops that go and come back, such as pulsing dots.
- easeSheet: cubic-bezier(0.32, 0.72, 0, 1)
- Long travel: Sheet, and Dialog on phones.ease-[cubic-bezier(0.32,0.72,0,1)]
Springs
- springSnappy: duration 0.24, bounce 0
- Small, fast follow motion: the item highlight size and sliding digits.
- springLayout: duration 0.3, bounce 0
- Layout motion: tab and toggle indicators, Stepper, PresenceList, and Button layout.
- springPop: visualDuration 0.28, bounce 0.12
- Parts that pop with a small overshoot: switch thumb, radio dot, slider thumb, and rating stars.duration-350 ease-spring
Push Move to compare the durations and springs.
Springs and tweens
A tween has a fixed duration and a curve. Use a tween for a fade, a color change, and an entrance or exit. CSS transitions are tweens.
A spring has no fixed curve. It keeps its velocity when the target changes during the animation. Use a spring for motion that follows the user or the layout, such as an indicator that moves to a new tab.
- The kit springs have no bounce, except
springPop. ease-springisspringPopas a CSSlinear()curve. Use it withduration-350, because the curve includes the time to settle.
CSS and Motion
Most state motion is CSS. React Aria sets a data attribute for each state, such as data-pressed, data-selected, data-entering, and data-exiting. Tailwind variants read these attributes. A nested part reads the state of its parent with a group variant, such as group-selected/switch:. Components do not copy state into React to animate it.
Popovers, menus, tooltips, and dialogs open and close with CSS on entering: and exiting:. CSS transitions keep running when the main thread is busy.
Use Motion only when CSS cannot do the work:
- Layout: an element moves to a new position in the layout, such as the tab indicator with
layoutId. - Presence: an element animates out before React removes it, such as Presence, PresenceList, and TextSwap.
- Values: a number counts to a new value.
- Gestures: a sheet follows a swipe.
Item highlight
Lists do not fill each row on hover. One highlight layer moves between the items. ListBox, Menu, Select, ComboBox, GridList, Tree, Table, Calendar, Tabs, Toolbar, and ToggleButtonGroup use it. It is in item-highlight.tsx.
- The layer follows the pointer and the focus-visible item. It moves with a transform on the
springSnappycurve, so it runs on the compositor. - If the target changes during a move, the layer starts again from its current position.
- The layer fades in and out with
fast. With reduced motion, it jumps to the item and still fades.
To add the layer to a React Aria collection, wrap its render with withItemHighlight. Do not add hover:bg-* to the items.
Loops
- loop.spin: 0.8 s, linear
- Spinner ring.
- loop.pulse: 1.1 s, easeInOut
- Spinner dots and segments, ToolCall, AgentStatus, and the prompt caret.animate-caret
- loop.sweep: 1.6 s, linear
- Skeleton shimmer and the indeterminate ProgressBar.
A loader is visible when the main thread is busy. Thus a loop must not need the main thread.
- Animate one
transformstring, notx,y,rotate, orscale. Motion then gives the animation to the browser, and it runs on the compositor. - Give the first keyframe, such as
rotate(0deg). Without it, Motion starts from a matrix and the spin does not turn. - Animate an HTML element or the root
<svg>, not an SVG child.
Reduced motion
Each component reads the prefers-reduced-motion setting. With reduced motion:
- CSS travel stops. Components put
motion-safe:on translate, scale, and rotate, ormotion-reduce:on the transition. Short fades and color changes stay. - Motion animations jump to the end.
transitionFor(reduceMotion, transition)gives a duration of 0 whenuseReducedMotion()is true. - Loops stop on a still frame. Skeleton shows no shimmer. Marquee does not scroll.
- Entrance reveals show the content immediately.
The right column below shows the reduced result. The dot jumps, because transitionFor gets true. The panel fades, but it does not move.
Full motion
Reduced motion
Tuned timings
Overlays and some components use their own timings. Each timing is tuned for one component. Keep these timings when you change the scale.
- Tooltip: 180 ms in, 120 ms out
- Tooltips open many times. A shorter animation does not slow the user.
- CommandPalette: 180 ms in, 120 ms out
- The keyboard opens it many times each day. It does not rise. It scales from 0.98.
- Popover: base in, fast out
- Menu, Select, ComboBox, date pickers, and ColorPicker use the Popover.
- Dialog: 260 ms in, 180 ms out
- The backdrop fades in 240 ms and out in 160 ms.
- Dialog on phones: 320 ms in, 240 ms out
- The panel slides up from the bottom edge on easeSheet.
- Sheet: 320 ms in, 240 ms out, 300 ms settle
- easeSheet. Settle is the snap back after a swipe.
- Toast: 350 ms in, 200 ms out
- The stack moves on ease-spring. A swipe moves the toast with the pointer.
- Chart: stiffness 380, damping 39
- A TanStack Charts spring with no bounce. It comes to rest in about 0.5 s. The plot fades in with slow.
- Presence: exit = 0.7 × duration
- Exits are shorter than entrances.
- Stagger: maxStagger 0.6 s
- The total delay of a TextReveal or StaggerGroup is not more than 0.6 s.
- Marquee and TextScramble: speed 50 px/s, duration 0.7 s
- Prop defaults. Linear.
To change the speed of all components, change the values in motion.ts and the duration classes in the component files.