BridgeHandbookDraft
For agents
Menu
Stable

Motion

Quiet motion: three speeds on one curve, what moves and how, and reduced motion.

Motion is quiet. It shows that something arrived, opened or changed, and then it gets out of the way. Three speeds on one curve cover everything; most of it comes with the components.

Three speeds, one curve

The bigger the thing that moves, the longer it takes: a color change is quick, a menu is a little slower, a drawer slower still. Everything uses one curve, fast out of the gate with a gentle landing, so the whole product moves alike.

The three speeds

Press Play. Each dot crosses at its speed, on the same curve.

Quick 150ms

Hover, color, press

Base 200ms

Menus, sections, toasts

Slow 300ms

Drawers, sheets, dialogs

SpeedTimeClassFor
Quick150msduration-150Hover, color, press
Base200msduration-200Menus, sections, toasts: small things opening
Slow300msduration-300Drawers, sheets, dialogs, page entrances: big things arriving
The curvecubic-bezier(0.23, 1, 0.32, 1)ease-outEverything. The package sets `ease-out` to it.

What moves, and how

MomentSpeed and motionDone by
Color or border changes under the pointerQuicktransition-colors duration-150
A button pressedQuick: shrinks to 98%Button does it
A clickable card hoveredQuick: rises 2px with a soft shadowCard does it
A menu, tip or popover opensBase: fades inthe component does it
A hover card opensBase: waits 300ms, then fades and grows in; closes 150ms after the pointer leavesHoverCard does it
A section opens or closesBase: slides to its height; what's below moves, never jumpsCollapsible does it
A toast arrivesBase: drops in from aboveToast does it
A drawer, dialog or bottom sheet opensSlow: slides or fades in over a dimming pagethe component does it
Cards appear when a page loadsSlow: fade up 12px, 50ms apart, oncethe app, with Framer Motion or animate-fade-in-up
A chart drawsSlow: bars grow from the baseline, onceon first show or a range change, never on a refresh

Reduced motion

  • When someone turns on reduced motion, the package stylesheet makes every CSS transition and animation instant. Loading spinners keep spinning: they say something is happening.
  • JavaScript animations aren't covered by that rule. With Framer Motion, check useReducedMotion() and skip the movement (show the content at once).
  • Nothing moves on its own for more than five seconds without a way to pause it, and nothing flashes more than three times a second.

Using motion

  • Use the three speeds and ease-out. No duration-500, duration-[250ms] or other curves in the app.
  • Animate once. Don't replay an entrance on hover, on polling or on every re-render.
  • Content that loads late must still show: animate it in unconditionally, not only when scrolled into view, or it can stay invisible.
  • Never animate to decorate or to pull the eye: no bouncing, pulsing or looping attention-grabbers.
  • Move with transform and opacity. Animating width, height or top makes the page stutter (Collapsible's height is the one exception, measured by Base UI).
// CSS: the speed and the curve
<div className="transition-opacity duration-200 ease-out" />

// Framer Motion in an app: the same values, and reduced motion honoured
import { useReducedMotion, motion } from "framer-motion";
import { transition } from "@brdgapp/ui/lib/motion";

const still = useReducedMotion();
<motion.div
  initial={still ? false : { opacity: 0, y: 12 }}
  animate={{ opacity: 1, y: 0 }}
  transition={transition("slow")}
/>

Rules

Quiet motion

Motion marks arrivals and presses, not decoration. Respect reduced motion: CSS animations are cut by the stylesheet, and JavaScript animations check useReducedMotion(). Loading spinners keep turning.