Skip to content

Motion

One animation language: five durations and three easings for CSS, four springs for JS. Every value ships as a token — durations collapse to 0ms under prefers-reduced-motion or the forced data-reduced-motion attribute, and JS springs read useReducedMotion.

Durations

CSS transitions read the duration vars directly, so the reduced-motion collapse needs no component code.

instant

--ch-motion-duration-instant · 80ms

State flips — checkbox marks, toggles

fast

--ch-motion-duration-fast · 160ms

Hover/focus color shifts, chips

base

--ch-motion-duration-base · 240ms

Standard transitions — accordions, tabs

slow

--ch-motion-duration-slow · 420ms

Panels, drawers, page-level moves

glacial

--ch-motion-duration-glacial · 800ms

Ambient/ceremonial motion only

<div className="transition-colors duration-[var(--ch-motion-duration-fast)]" />
  • --ch-motion-duration-instant
  • --ch-motion-duration-fast
  • --ch-motion-duration-base
  • --ch-motion-duration-slow
  • --ch-motion-duration-glacial

Easings

Cubic-bezier curves; tide is the signature draw.

out

cubic-bezier(0.16, 1, 0.3, 1)

Default — decisive arrivals

in-out

cubic-bezier(0.65, 0, 0.35, 1)

Symmetric moves within a view

tide

cubic-bezier(0.32, 0.72, 0, 1)

Signature — long draws, drawers, harbor swells

<div className="transition-transform ease-[var(--ch-motion-ease-tide)] duration-[var(--ch-motion-duration-slow)]" />
  • --ch-motion-ease-out
  • --ch-motion-ease-in-out
  • --ch-motion-ease-tide

Springs

The beUI-derived house springs, exported as @coldharbor/tokens/motion. JS animation must import these — never inline stiffness/damping.

press

stiffness 600 · damping 30

Press feedback on controls

glide

stiffness 380 · damping 34

Shared-layout pills, sliding indicators

unfold

stiffness 260 · damping 26

Expanding surfaces — accordions, sheets

bounce

stiffness 500 · damping 18

Playful arrivals — badges, confirmations

import { springs } from "@coldharbor/tokens/motion";

<motion.div animate={{ x }} transition={springs.glide} />
  • springs.press
  • springs.glide
  • springs.unfold
  • springs.bounce

Reduced motion

Left: live tokens. Right: the same demos inside a data-reduced-motion="reduce" wrapper — the duration vars collapse to 0ms in CSS, and JS springs drop to duration 0 via useReducedMotion / the forced app setting.

Motion on

base duration + glide spring at full value.

data-reduced-motion=reduce

Same components, zero movement — position still updates instantly, nothing is lost but the choreography.

/* CSS side — free: the vars collapse to 0ms. */
@media (prefers-reduced-motion: reduce) { … }
[data-reduced-motion="reduce"] { --ch-motion-duration-base: 0ms; … }

/* JS side */
const reduce = useReducedMotion();
<motion.div transition={reduce ? { duration: 0 } : springs.glide} />
  • --ch-motion-duration-instant
  • --ch-motion-duration-fast
  • --ch-motion-duration-base
  • --ch-motion-duration-slow
  • --ch-motion-duration-glacial