Conventions
The rules the packages hold to. They are what keep a system this size coherent — and what a contribution is reviewed against.
No hardcoded color
Color drift is the failure this system exists to prevent.
Use the token utility classes — bg-card, text-muted-foreground, bg-primary/15, border-border — never a literal hex. The two sanctioned exceptions are the pre-paint theme script (it runs before CSS loads) and the chart theme bridge (a canvas can't read CSS variables, so it reads them via getComputedStyle, live).
Do: <div className="bg-card text-foreground border-border" />
Don’t: <div style={{ background: "#f9faf9" }} />
One source for motion
Import EASE / SPRING / DURATION from @shuttering/motion; never retype a curve.
import { EASE, DURATION } from '@shuttering/motion'
transition={{ duration: DURATION.base, ease: EASE.out }}Every animated surface handles reduced motion — three layers back each other up: MotionProvider sets Motion's reducedMotion="user", components branch on useReducedMotion, and theme.css carries a prefers-reduced-motion backstop. Animations never start from scale(0); they begin at 0.96.
Every control is reachable by touch
Target size is a separate claim from visual size.
Interactive controls declare --spacing-tap at pointer: coarse — by growing the control (pointer-coarse:min-h-tap) or, where the paint stays small, by growing the hit area alone, which takes a positioned box and all three of after:absolute, after:-inset-N and after:content-['']. A pseudo-element without content generates no box, so an inset on its own expands a target that was never drawn. A hover-only affordance satisfies nothing either: every hover: utility compiles inside @media (hover: hover), so anything reachable only that way cannot be reached by touch at all.
Animate only what earns it
Causality, continuity, feedback — or nothing.
Before adding motion, name what it carries: causality (the thing came from somewhere), continuity (this is the same object), or feedback (the press registered). Anything else is decoration. Frequency argues against motion rather than for it — a surface crossed dozens of times a session pays the duration every time. If the purpose does not fit in one word, don't build it.
Every control has a focus state
Nothing is focusable without being visible.
Interactive controls render a visible keyboard-focus indicator (focus-visible:ring-2 focus-visible:ring-ring/50, with a ring offset on filled controls). See Agent surfaces and the accessibility baseline for the full commitment.
Strict TypeScript
The tsconfig is strict, and the strictness has consequences worth knowing up front.
import typefor type-only imports; relative imports carry the.jsextension even from.tsx.- Optional props are conditionally spread, never set to
undefined(exactOptionalPropertyTypes). - Indexed access is
T | undefined— guard it (noUncheckedIndexedAccess). no-explicit-anyis an error; external option shapes are typed.
Component shape
React 19 conventions across the library.
- No
forwardRef, noReact.FC; props inline, extendingReact.ComponentProps<'x'>. - Each component carries a
data-slot; state is expressed asdata-state/data-activeattributes and selected in classes, not toggled class names. - Styling goes through
cnfrom@shuttering/utilsand the token utilities.