Skip to content

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.

fast
150 ms
base
200 ms
slow
250 ms
springPop
350 ms
springSnappy
0.24 s
springLayout
0.3 s
motion-scale-demo.tsx

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-spring is springPop as a CSS linear() curve. Use it with duration-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.

popover.tsx

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

React Aria styling guide (opens in a new tab)

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 springSnappy curve, 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.

Stable
Beta
Canary
Nightly
motion-highlight-demo.tsx

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 transform string, not x, y, rotate, or scale. 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.
spinner.tsx

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, or motion-reduce: on the transition. Short fades and color changes stay.
  • Motion animations jump to the end. transitionFor(reduceMotion, transition) gives a duration of 0 when useReducedMotion() 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

3 events this week

Reduced motion

3 events this week
motion-reduced-demo.tsx

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.