Getting Started
Three CSS files and two patterns cover every use case.
Import, reference a token, apply a component class.
Import
Add three stylesheet links in order — tokens must load before shared, and shared before components.
<!-- 1. Primitive + semantic tokens -->
<link rel="stylesheet" href="tokens.css">
<!-- 2. Layout, header, hero, footer -->
<link rel="stylesheet" href="shared.css">
<!-- 3. All component classes -->
<link rel="stylesheet" href="components.css">
Using a token
Reference semantic tokens in your CSS — never raw primitives.
.my-element {
color: var(--color-interactive-primary-text);
/* resolves to --color-blue-600 */
gap: var(--gap-md);
/* resolves to 16px */
}
Always consume semantic tokens. Primitives like --color-blue-600 may change.
Token Architecture
Three-tier token system — primitives, type tokens, and semantic color/spacing tokens — with full light and dark mode support.
Token Map
Mapping between Figma variable paths and their CSS custom property equivalents.
Click any CSS variable or value to copy it.
| Figma path | CSS variable | Resolves to |
|---|---|---|
| color/text/primary | --color-text-primary | neutral-900 |
| color/text/secondary | --color-text-secondary | neutral-600 |
| color/text/ghost | --color-text-ghost | neutral-500 |
| color/text/inverse | --color-text-inverse | white |
| color/text/buttons/disabled | --color-text-buttons-disabled | neutral-300 |
| color/text/link/default | --color-text-link-default | blue-600 |
| color/text/accent | --color-text-accent-default | blue-500 |
| color/text/accent-dim | --color-text-accent-dim | blue-400 |
| color/text/accent-subtle | --color-text-accent-subtle | blue-300 |
| color/text/hero/dim | --color-text-hero-dim | neutral-400 |
| color/text/hero/ghost | --color-text-hero-ghost | neutral-400 |
| color/stroke/default | --color-borders-default | neutral-200 |
| color/stroke/strong | --color-borders-strong | neutral-600 |
| color/stroke/focus | --color-borders-focus | blue-400 |
| color/stroke/accent/default | --color-borders-accent-default | blue-500 |
| color/stroke/accent/dim | --color-borders-accent-dim | blue-400 |
| color/stroke/hero | --color-borders-hero | rgba(255,255,255,0.06) |
| color/stroke/buttons/danger/default | --color-borders-buttons-danger-default | transparent |
| color/stroke/buttons/danger/hover | --color-borders-buttons-danger-hover | transparent |
| color/stroke/buttons/danger/active | --color-borders-buttons-danger-active | transparent |
| color/surface/page-bg | --color-surface-page-bg | neutral-50 |
| color/surface/card | --color-surface-card | white |
| color/surface/hero | --color-surface-hero | neutral-800 |
| color/surface/accent/default | --color-surface-accent-default | blue-500 |
| color/surface/accent/dim | --color-surface-accent-dim | blue-400 |
| color/buttons/danger/default | --color-buttons-danger-default | red-500 |
| color/buttons/danger/hover | --color-buttons-danger-hover | red-600 |
| color/buttons/danger/active | --color-buttons-danger-active | red-700 |
| color/interactive/primary/text | --color-interactive-primary-text | blue-600 |
| color/interactive/primary/default | --color-interactive-primary-default | blue-400 |
| color/interactive/primary/hover | --color-interactive-primary-hover | blue-300 |
| color/interactive/subtle | --color-interactive-subtle | blue-50 |
| Figma path | CSS variable | Value |
|---|---|---|
| fontsize/headings/h1 | --font-size-h1 | 90px |
| fontsize/headings/h2 | --font-size-h2 | 67px |
| fontsize/headings/h3 | --font-size-h3 | 51px |
| fontsize/headings/h4 | --font-size-h4 | 38px |
| fontsize/headings/h5 | --font-size-h5 | 28px |
| fontsize/headings/h6 | --font-size-h6 | 21px |
| fontsize/body/lg | --font-size-body-lg | 21px |
| fontsize/body/md | --font-size-body-md | 16px |
| fontsize/body/sm | --font-size-body-sm | 14px |
| fontsize/body/xs | --font-size-body-xs | 12px |
| Figma path | CSS variable | Value |
|---|---|---|
| type/font weight/light | --font-weight-light | 300 |
| type/font weight/regular | --font-weight-regular | 400 |
| type/font weight/medium | --font-weight-medium | 500 |
| type/font weight/semibold | --font-weight-semibold | 600 |
| type/font weight/bold | --font-weight-bold | 700 |
| CSS variable | Value | Use | |
|---|---|---|---|
| line height/ratio/tight | --line-height-tight | 1 | display, counters, large headings |
| line height/ratio/snug | --line-height-snug | 1.2 | compact text, sidebar, mono labels |
| line height/ratio/relaxed-sm | --line-height-relaxed-sm | 1.33 | small text xs/sm |
| line height/ratio/base | --line-height-base | 1.5 | standard body text |
| line height/ratio/relaxed | --line-height-relaxed | 1.6 | comfortable body |
| line height/ratio/loose | --line-height-loose | 1.65 | spacious descriptions, cards |
| line height/ratio/prose | --line-height-prose | 1.75 | article body, section leads |
| line height/ratio/code | --line-height-code | 1.85 | code blocks, dense mono |
| Figma path | CSS variable | Value |
|---|---|---|
| sizes/gap/none | --gap-none | 0px |
| sizes/gap/px | --gap-px | 1px |
| sizes/gap/xs | --gap-xs | 4px |
| sizes/gap/sm | --gap-sm | 8px |
| sizes/gap/md | --gap-md | 16px |
| sizes/gap/lg | --gap-lg | 24px |
| sizes/gap/xl | --gap-xl | 32px |
| sizes/gap/2xl | --gap-2xl | 48px |
| sizes/gap/3xl | --gap-3xl | 64px |
| sizes/gap/4xl | --gap-4xl | 80px |
| sizes/gap/5xl | --gap-5xl | 96px |
| sizes/gap/section | --gap-section | 128px |
| sizes/gap/layout | --gap-layout | 192px |
| sizes/gap/page | --gap-page | 256px |
| Figma path | CSS variable | Value |
|---|---|---|
| sizes/border/none | --border-none | 0px |
| sizes/border/br-1 | --border-1 | 1px |
| sizes/border/br-2 | --border-2 | 2px |
| sizes/border/br-4 | --border-4 | 4px |
Color System
7 families built in OKLCH — perceptually uniform lightness steps from 50 (near-white) to 900 (near-black). Hover swatches for values.
scrim · disabled-surface
Typography
Three-font system: Bebas Neue for display and headings, Barlow for body and UI, JetBrains Mono for code and technical content. Scale follows the Perfect Fourth modular ratio (× 1.333).
Perfect Fourth derives every size by multiplying or dividing the base (16 px) by 4/3. The result is a geometric progression — each step is exactly 33% larger than the one below it — giving the scale a natural, harmonic rhythm without arbitrary intermediate values. Heading sizes grow quickly (21 → 28 → 38 → 51 → 67 → 90 px) to create clear visual hierarchy, while body sizes stay close to the base for comfortable reading.
Neue
0123456789 & ! @ # $ % ^ *
0123456789 → Il1 O0 :;.,
Medium Semibold
The quick brown fox jumps over the lazy dog.
Display
| Style | Token | Visual | Value |
|---|---|---|---|
| H3 | --paragraph-spacing-h3 | 32px | |
| H4 | --paragraph-spacing-h4 | 24px | |
| H5 | --paragraph-spacing-h5 | 20px | |
| H6 | --paragraph-spacing-h6 | 16px | |
| Body LG | --paragraph-spacing-body-lg | 32px | |
| Body MD | --paragraph-spacing-body-md | 24px | |
| Caption | --paragraph-spacing-caption | 18px |
Spacing
Named gap tokens from 4px (--gap-xs) to 160px (--gap-layout), organised by use context.
Always reference a token — never hardcode spacing values.
Corner Radius
Nine named values from 4px to pill (9999px).
Apply via --radius-* to keep rounding consistent across components.
Dividers
Three weight variants — default, strong, and subtle — plus a vertical option for inline layouts.
Principles
Three tenets that govern every animated element in the system.
Purposeful, perceptible, and always accessible.
Every animated element serves a function — confirming an action, revealing content, or guiding focus. Motion never exists purely for decoration.
Animations are fast enough to feel responsive but slow enough to be understood. Duration and easing are chosen for clarity, not effect.
All animations respect prefers-reduced-motion. Users who opt out of motion get an equivalent non-animated experience.
Duration Scale
Three named durations cover the full range of UI interactions.
Avoid hardcoding millisecond values — always reference a token so the system can tune motion globally.
/* Duration tokens */ --dur-fast: 240ms; /* hover, micro-interactions */ --dur-base: 500ms; /* standard entrance / exit */ --dur-slow: 720ms; /* hero reveals, page transitions */
Easing Functions
Easing functions define the feel of motion.
All three are expo-family curves to keep the system coherent.
/* Easing tokens */ --ease-expo: cubic-bezier(0.16, 1, 0.3, 1); /* expo-out — enter */ --ease-quart: cubic-bezier(0.25, 1, 0.5, 1); /* quart-out — hover */ --ease-exit: cubic-bezier(0.4, 0, 1, 1); /* accelerate — exit */
Keyframe Catalog
Three reusable @keyframes for entrances and ambient loops.
Always reference by name — never define new keyframes outside this catalog.
Fades in while translating 24 px upward. Used for scroll reveals, modal content, and staggered list entrances.
Pure opacity transition from 0 → 1. Ideal for overlays, tooltips, and elements with no spatial movement.
Looping vertical oscillation with opacity fade. Used on scroll indicators in hero sections.
Composition
Any animation is a combination of a duration and an easing.
The matrix below maps which pairings are recommended, acceptable, or discouraged.
| Duration | ease-expo | ease-quart | ease-exit |
|---|---|---|---|
| --dur-fast · 240ms | ✓ Recommendedhover, badges, toggles | ✓ Goodcolor transitions | ✓ Recommendedquick exits |
| --dur-base · 500ms | ✓ Recommendedscroll reveals, modals | ✓ Goodlayout shifts | ⚠ Cautionfeels abrupt at 500ms |
| --dur-slow · 720ms | ✓ Recommendedhero, page load | ✓ Goodambient motion | ✗ Avoidtoo fast to notice |
Reduced Motion
Users may set a system-level preference to reduce motion.
FORMA respects this with global overrides in every page and a standard pattern for custom animations.
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } .reveal { opacity: 1; transform: none; } }
| Animation | Standard behavior | Reduced motion fallback |
|---|---|---|
| slide-up | Fades + translates 24 px up over 720ms | Instant appearance, no movement |
| fade-in | Opacity 0 → 1 over 500ms | Instant appearance at full opacity |
| scroll-hint | Looping vertical oscillation | Static icon, no loop |
| hover transitions | Color, background, shadow in 240ms | Instant state change |
| count-up (JS) | Number animates from 0 over 900ms | Final value rendered immediately |
Layout
Page-level layouts provide consistent structure. All use CSS grid with token-based gaps and respect the 220 px sidebar offset.
Interaction States
Every interactive component moves through the same state model. Consistent states reduce cognitive load and make the system predictable.
Data Display
| Token | Category | Value | Status |
|---|---|---|---|
| --color-blue-500 | Color · Primitive | #127ae6 | Stable |
| --dur-fast | Motion · Duration | 240ms | Stable |
| --radius-12 | Border Radius | 12px | Stable |
| --gap-section | Spacing · Layout | 120px | Review |
| --shadow-xl | Effect · Elevation | 0 24px 48px … | Draft |
Feedback
Info
Use this pattern to surface neutral context or informational tips to the user.
Success
Confirms a completed action. Keep the message short and specific.
Warning
Flags a potential issue that doesn't block the user but requires attention.
Error
Communicates a failure. Always explain what went wrong and how to fix it.
No tokens found
Try a different search term, or add a new token to the system.
Forms
Form patterns cover field states, inline validation, and two standard layouts.