Motion contract
Animation is on by default here, which is why the timings are tokens: a component reads
--cb-duration-base or asks the provider for a spring, and never writes its own numbers. Without
that rule twenty components drift into five different rhythms.
Tokens
| Token | Value | Used for |
|---|---|---|
--cb-duration-instant | 80ms | State that must feel immediate: press, focus ring |
--cb-duration-fast | 140ms | Hover, colour changes |
--cb-duration-base | 220ms | Enter and exit of overlays |
--cb-duration-slow | 380ms | Larger movement, drawers |
--cb-duration-deliberate | 620ms | Things meant to be watched: a ring filling |
Springs are named too — snappy for controls, soft for surfaces, bouncy for celebratory moments.
MotionProvider
The provider wraps an application, so it is written out rather than staged. The footer under every example on this site reports what it currently resolves to.
import { MotionProvider, useMotionSettings } from '@ceebee/ui/client';
<MotionProvider scale={0.5}> {/* everything at half speed */} <App /></MotionProvider>
function Card() { const { enabled, duration, spring } = useMotionSettings(); return <motion.div animate={{ opacity: 1 }} transition={spring('soft')} />;}| Prop | Type | Default | Notes |
|---|---|---|---|
| enabled | boolean | true | App-level off switch — a settings toggle, or a test. |
| scale | number | 1 | Multiplier on every duration. 0 is equivalent to disabled. |
Reduced motion
prefers-reduced-motion: reduce is treated as a requirement, not a hint. It resolves enabled to
false, collapses every duration to zero, and switches CSS animations off through
--cb-motion-scale.
Do
Drop transforms and keep opacity, so the state change is still visible without movement.
Don't
Remove the feedback entirely — a dialog that appears with no transition at all is worse than one that fades.
Every animated component also takes motion={false} for a single opt-out that does not touch the
provider.
Reveal and Stagger
Two primitives for content that arrives rather than pops. Both collapse to a plain fade under reduced motion — the transform is what goes, not the feedback.
Reveal animates one element on mount or on scroll into view. Stagger wraps each child in a
Reveal with an increasing delay, so a list assembles instead of appearing all at once. Reload the
page to watch them run again.
Arrived from below
Kebayoran Baru
Menteng
Senopati
Cipete
| Prop | Type | Default | Notes |
|---|---|---|---|
| from | 'below' | 'above' | 'left' | 'right' | 'none' | 'below' | Direction travelled from. Ignored under reduced motion. |
| distance | number | 12 | Travel in px. Keep it small — large entrances read as sluggish. |
| delay | number | 0 | Seconds before it starts. |
| spring | 'snappy' | 'soft' | 'bouncy' | 'soft' | Spring preset from the motion tokens. |
| onView | boolean | false | Waits until it scrolls into view, and fires once. |
| step | number | 0.06 | Stagger only: seconds between children. |