Photon Design System

Motion

Every animation in Photon speaks one vocabulary: five springs, three timings, two CSS eases, a handful of distances. Components import these named tokens and add nothing of their own — a new surface that moves should sound like the rest of the site. The grammar in one breath: arrive on the entrance spring, swap through an ease-out fade, travel linearly, press on a spring with mass, follow content on the settle spring.

Click a card and the ball speaks its token.

Import

import {
  SPRING_ENTRANCE,
  FADE_SWAP,
  TRAVEL_LINEAR,
  fadeThroughBlur,
} from "@photon-ai/pho-ui/motion";

Springs

Timings

Fades ease out; displacement is linear. When one move carries both — the condensed bar rising in — spread them per channel:

transition={{ ...FADE_SWAP, y: TRAVEL_LINEAR }}

Presses

Three press springs, by the size of what’s under the pointer. Depth comes from PRESS_SCALE (0.96, small controls) and PRESS_SCALE_ROW (0.97, rows and full-size surfaces).

CSS eases

Where the animation lives in a stylesheet (Base UI popups, the toast stack), the curve is a token too.

Recipes

Two spreads cover most compositions — both respect reduced motion on their own.

const reduceMotion = useReducedMotion();

<motion.div {...riseIn(reduceMotion)} />          // arrive like a Stack

<AnimatePresence initial={false}>
  <motion.div key={face} {...fadeThroughBlur(reduceMotion)} />
</AnimatePresence>

Distances

TokenValueWhere
RISE_PX12Entrances rise from just below.
BAR_RISE_PX−8The condensed bar comes from nearer still.
BLUR_PX1The blur budget — one breath, everywhere.
PRESS_SCALE0.96Press depth for small controls.
PRESS_SCALE_ROW0.97Press depth for rows and full-size surfaces.

Guidelines

  • Reach for a token before writing a number; if a surface needs a new number, it probably wants a new name here first.
  • Fades ease out, travel is linear — never the other way around.
  • Arrive, don’t slide: entrances rise RISE_PX, not a screen-width.
  • One breath of blur (BLUR_PX) is the whole budget; more reads as glass.
  • Every recipe collapses under reduced motion — pass useReducedMotion() through, never guard by hand.