Skip to main content

Tokens

Every value in the system, and the name it answers to. tokens/tokens.json is the authoring source: tools/sync.mjs generates the CSS custom properties, the typed TS theme, the React Native theme and the Tailwind preset from it, and this page is read from the same file — so a value cannot be documented as one thing and shipped as another.

Reaching them

Four forms, one source. Take whichever your platform can read.

/* CSS custom properties — the six web libraries */
color: var(--hg-fg);
background: var(--hg-surface);
padding: var(--hg-space-16);
border-radius: var(--hg-radius-md);
// A typed object, for a canvas, a chart, or <meta name="theme-color">
import { light, dark, cssVar, space, radius, motion } from "./hourglass/theme/tokens";

light.accent; // "#242424"
cssVar.accent; // "var(--hg-accent)"
space[16]; // 16
motion.easing.out; // "cubic-bezier(0.16, 1, 0.3, 1)"
// React Native, where there are no custom properties to cascade
const theme = useTheme();
theme.colors.surface;
theme.target.min; // 44 on iOS, 48 on Android
// Optional: every token mapped onto Tailwind's scales
presets: [require("./hourglass/tailwind.preset")];

The two theme files stand alone. hourglass-tokens.css is useful without a single component — link it and dark mode already follows the OS, with no JavaScript.

Colour

OKLCH is the source of truth; the hex beside it is the sRGB fallback, used verbatim by React Native. Monochrome throughout — accent is near-black in light and near-white in dark, the same neutral scale rather than a hue.

TokenCSSLightDark
bg--hg-bg#fcfcfcoklch(99% 0 0)#141414oklch(12% 0 0)
surface--hg-surface#ffffffoklch(100% 0 0)#1f1f1foklch(16% 0 0)
elevated--hg-elevated#f5f5f5oklch(97% 0 0)#292929oklch(20% 0 0)
border--hg-border#dcdcdcoklch(89% 0 0)#393939oklch(27% 0 0)
borderStrong--hg-border-strong#b4b4b4oklch(75% 0 0)#5c5c5coklch(42% 0 0)
fg--hg-fg#1a1a1aoklch(15% 0 0)#f5f5f5oklch(97% 0 0)
muted--hg-muted#666666oklch(46% 0 0)#9a9a9aoklch(66% 0 0)
faint--hg-faint#9d9d9doklch(67% 0 0)#616161oklch(44% 0 0)
accent--hg-accent#242424oklch(20% 0 0)#edededoklch(94% 0 0)
accentFg--hg-accent-fg#fcfcfcoklch(99% 0 0)#191919oklch(14% 0 0)

Derived

Mixes of the colours above, so overriding a base carries through instead of leaving these stranded. On the web they are color-mix(); React Native gets the resolved value.

TokenCSSLightDark
accentSoft--hg-accent-softrgba(36, 36, 36, 0.12)accent at 12% over transparentrgba(237, 237, 237, 0.12)
accentDim--hg-accent-dim#e0e0e0accent at 14% over surface#3c3c3c
accentStrong--hg-accent-strong#222222accent at 82% over fg#eeeeee
fgSoft--hg-fg-softrgba(26, 26, 26, 0.06)fg at 6% over transparentrgba(245, 245, 245, 0.06)

Type

TokenCSSStack
display--hg-font-displayInter, system-ui, -apple-system, "Segoe UI", sans-serif
body--hg-font-bodyInter, system-ui, -apple-system, "Segoe UI", sans-serif
mono--hg-font-mono"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace

The libraries ship no font files — a folder you copy into someone else's project has no business carrying licensed binaries. Load Inter and JetBrains Mono in the host app, or accept the fallbacks; the scale works either way.

StepCSSSizeWeightTrackingFamily
hero--hg-text-heroclamp(40px, 12vw, 96px)600-0.02emdisplay
h1--hg-text-h128px600-0.01emdisplay
h2--hg-text-h222px600-0.01emdisplay
h3--hg-text-h318px600-0.01emdisplay
body--hg-text-body14px4000body
small--hg-text-small13px4000body
caption--hg-text-caption11px5000.1emmono · uppercase
micro--hg-text-micro10px5000.08emmono

10px is the floor. micro is the smallest step, and nothing renders below it.

Space

StepCSSValueNotes
2--hg-space-22px
4--hg-space-44px
8--hg-space-88px
12--hg-space-1212px
16--hg-space-1616px
20--hg-space-2020px
24--hg-space-2424px
32--hg-space-3232px
44--hg-space-4444px
64--hg-space-6464px
gutter--hg-gutterclamp(16px, 4vw, 44px)The page margin. Grows with the viewport.

Radius

StepCSSValue
sm--hg-radius-sm8px
md--hg-radius-md12px
lg--hg-radius-lg16px
xl--hg-radius-xl22px
2xl--hg-radius-2xl28px
pill--hg-radius-pill100px

Hit targets

PlatformCSSValueNotes
ios--hg-target44pxThe iOS floor, and the default.
android--hg-target-android48pxAndroid asks for more; React Native picks the right one per platform.

Every interactive element is at least this tall and wide, padding included. React Native resolves the platform's own figure; the web takes 44.

Elevation

TokenCSSLightDarkUsed for
sm--hg-shadow-sm0 1px 2px rgba(26, 26, 26, 0.06), 0 8px 30px rgba(26, 26, 26, 0.06)0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 30px rgba(0, 0, 0, 0.4)A menu, a popover — something a few pixels off the page.
lg--hg-shadow-lg0 10px 30px rgba(26, 26, 26, 0.08), 0 40px 90px rgba(26, 26, 26, 0.12)0 10px 30px rgba(0, 0, 0, 0.55), 0 40px 90px rgba(0, 0, 0, 0.65)A sheet or a toast, the only things that genuinely float.

React Native splits each shadow into shadowColor, shadowOffset, shadowOpacity and shadowRadius for iOS, plus elevation for Android — see the React Native theme.

Motion

TokenCSSValueUsed for
base--hg-duration-base500msThe default. Anything entering or leaving.
fast--hg-duration-fast300msA state change on something already on screen.
stagger--hg-stagger70msThe gap between siblings in a cascade.
pulse--hg-duration-pulse1200msOne breath of a live indicator.
dialSweep--hg-duration-dial800msA dial arc drawing itself.
reelDigitStagger--hg-reel-digit-stagger55msThe gap between digits in a reel, left to right.

Easing

TokenCSSCurveReact Native
out--hg-ease-outcubic-bezier(0.16, 1, 0.3, 1)ease-out exponentialEasing.out(Easing.exp)
reel--hg-ease-reelcubic-bezier(0.25, 1, 0.5, 1)ease-out quartEasing.out(Easing.poly(4))

Under prefers-reduced-motion the duration tokens collapse to 0.01ms, so every animation lands on its final state with no travel — and nothing has to check a media query to make that happen. See Motion.

Contrast floors

FloorRatioApplies to
body4.5:1Anything that is a sentence.
large3:118px and up, or 14px semibold and up.
icon3:1A glyph carrying meaning, and the edge of a control.

Held in both modes. Disabled is the only state permitted to drop below the floor; the web shows a visible focus ring; and mono numerals carry a spoken label, so a screen reader does not read 4:40 as digits.

Next

Components — all 23, with props, types and code samples.