Motion
Orbital, and always explaining a change. Things settle into place on springs, light circles what is working, and nothing moves at idle unless it is live.
Durations
Six steps from instant to focal. Exits run faster than entrances: a menu opens in 200ms and closes in 110ms, a dialog arrives in 320ms and leaves in 150ms.
--o-dur-instant90msA tooltip leaving; the next tooltip along a toolbar--o-dur-fast150msHover and colour changes; most exits--o-dur-base220msFades behind overlays; shadows--o-dur-slow320msA dialog arriving; a press springing back--o-dur-slower480msThe sidebar folding to its rail; the phone drawer--o-dur-focal720msAn area chart drawing its line: the one authored momentEasings
Arrivals take the confident ease-out or the settle spring. Authored moments take the Orbit curve. The two springs are sampled into linear(), so CSS can settle with an overshoot: about 3% for settle and 12% for pop. The curves below are drawn from the live tokens.
--o-ease-outMost state changes: confident, then gentle.--o-ease-inThings on their way out, such as a tooltip.--o-ease-in-outA move between two resting places: the comet's pass.--o-ease-brandAuthored moments. Shared with orbitcommerce.net.--o-ease-springArrivals with a whisper of overshoot: overlays, presses.--o-ease-spring-bouncySmall confirmations only: a count changing, a dot landing.Springs in JavaScript
Motion driven from React uses motion/react with the presets from the package, so a shared layout glides on the same spring everywhere. Figures are stiffness and damping, then mass where it is not 1.
spring.snappy500/38Pills, thumbs and indicators: the nav pill, the segmented thumb.spring.soft320/34Panels, drawers, the command palette opening.spring.gentle220/30Large layout moves.spring.land700/53Critically damped, so it lands exactly: the palette folding back into its pill.spring.bouncy600/18/0.8Small celebratory pops.| Export | Values |
|---|---|
spring | snappy, soft, gentle, land and bouncy, as motion transitions. |
ease | out [0.16, 1, 0.3, 1], inOut [0.65, 0, 0.35, 1], brand [0.625, 0.05, 0, 1] |
duration | In seconds, matching the CSS steps: instant 0.09, fast 0.15, base 0.22, slow 0.32, slower 0.48, focal 0.72. |
Signature moments
Four moments carry the brand. Each lives in a component; try them here.
The comet
Hover or focus the primary pill: a point of light with a fading tail passes once around it in 1.35s. While the action runs, it stays in orbit. See Button.
The gliding nav pill
One white pill marks the current page and glides between pages; one soft pill follows the pointer down the list. Both share a layout on the snappy spring, so the eye follows a single shape. Choose a page. See App shell.
Rolling digits
Every digit is a reel. A change rolls each column like an odometer in 0.9s on the settle spring, each column to the right starting 25ms later. See Rolling number.
The area chart arrival
The one authored moment: the chart draws its line from the first day to the last in 720ms, and the area fills in behind it. After that, only live things move. See Area chart.
Choreography
Overlays grow out of their trigger, scaling from 0.96 with a fade, and leave faster than they came. The 12% pop is kept for small confirmations: a count changing, a check landing.
| Moment | In | Out |
|---|---|---|
| Menu and popover | Grows from the trigger in 200ms on the settle spring; rows rise 2px, 15ms apart | 110ms |
| Tooltip | Grows in 140ms with a 3px drift; the next one along a toolbar fades in 90ms | 90ms |
| Dialog | Rises 10px and settles in 320ms | 150ms |
| Command palette | Grows out of the search pill on the soft spring | Folds back on the land spring |
| Empty state | The mark settles in; the words rise 6px after it, over 480ms | None |
| Nav pill, segmented thumb, figure underline | Glide on the snappy spring | None |
- The Explain-a-Change Rule. Motion explains a state change, gives feedback or keeps continuity. Nothing loops at idle except live indicators and loading.
- The Pop Budget Rule. Only glyph-sized things pop. Surfaces, sheets, buttons, thumbs and text always settle.
- Exits are faster than entrances, and never bounce.
Reduced motion
The Stillness Rule: every component carries its own reduced-motion version. Movement (translate, scale, rotate, orbit) is dropped while opacity and colour stay, so every change of state is still legible.
| Moment | Full motion | Reduced motion |
|---|---|---|
| Button press | Dips to 0.965 and springs back | Colour changes only |
| The comet on hover | Passes once around the pill | Stays hidden |
| The comet while loading | Stays in orbit | Parks at one angle and breathes |
| Menus | Grow from the trigger, rows rise | Fade in |
| Tooltips and dialogs | Grow or rise into place | Fade |
| Nav pill, segmented thumb | Glide | Move straight to the new place |
| Rolling digits | Roll like an odometer | Swap with a fade |
| Live dots | Send out a ring | Breathe in place |
| Area chart | Draws its line, the area filling in behind | Appears drawn |
| Skeleton and spinner | A sheen passes; the satellite circles | The skeleton holds still; the spinner breathes |
In code
.order-row {
transition:
background-color var(--o-dur-fast) var(--o-ease-out),
transform var(--o-dur-slow) var(--o-ease-spring);
}
.order-row:active {
transform: scale(0.99);
}
/* Last in every stylesheet: drop the movement, keep the colour. */
@media (prefers-reduced-motion: reduce) {
.order-row {
transition-property: background-color;
}
.order-row:active {
transform: none;
}
}import { motion, useReducedMotion } from 'motion/react'
import { spring } from '@orbitcommerce/constellation'
function ActiveMark() {
const reduce = useReducedMotion()
return (
<motion.span
layoutId="active-mark"
transition={reduce ? { duration: 0 } : spring.snappy}
/>
)
}