` for navigation. Never the reverse, never ``.
- Icon-only buttons need `aria-label`.
- Icons must be wrapped: `
` — never bare `
`, ``, or ` `. Brand icons only.
- Use the page template at `templates/page-template.html` as the starting point for new pages — it includes the required SEO meta tags.
## Theme Defaults
- **Default is light mode.** Never apply dark mode unless explicitly told to.
- Dark mode values (e.g. `#1a1a1a`, `#e8e6e3`) are overrides applied via `[data-theme="dark"]` — they are not defaults. Do not use them as primary values.
- Dark mode is enabled by setting `data-theme="dark"` on a container element (typically ``, but it can be scoped to any wrapper). Tokens inherit through the cascade — never write component-level dark mode CSS.
- **If a screenshot shows a dark UI alongside a light-default design system, ask which theme is intended before generating any code.** Do not infer theme from visuals.
## Never Hardcode Values
The design system has two sets of colour values — light and dark. Hardcoding either set breaks theming.
```css
/* Never */
background: #1a1a1a;
color: #e8e6e3;
/* Always */
background: var(--background-primary);
color: var(--text-primary);
```
The same applies to spacing, typography, borders, and radii — every visual value must reference a token from the Design Tokens section below.
## Do Not
- Apply dark mode by default — light mode is the default unless explicitly requested
- Infer theme from a screenshot — if it looks dark, ask before generating code
- Hardcode hex, pixel, or raw font values that should use tokens
- Use primitive color tokens directly (e.g., `--green`, `--neutral-800`, `--off-white`)
- Use inline styles (the styleguide demo files are the only exception)
- Use external icon libraries (Font Awesome, Material Design, Heroicons, etc.) — only brand SVG icons
- Add margins inside blocks — blocks use `gap` for internal spacing
- Add spacing directly to containers or sections — section uses `.top-*` / `.bottom-*`, padding lives on `.padding-global`
- Use spacer divs
- Invent new class naming patterns — follow `.component-name` / `.component-name--modifier` / `.is-state`
- Skip layout-hierarchy levels — never put a `.block` directly inside a ``
- Write component-level dark mode CSS — `[data-theme="dark"]` handles it via tokens
---
## Layout System
This system provides a **predictable layout structure** that keeps spacing, alignment, and width consistent across all pages.
---
## Page Structure
All pages follow the same hierarchy:
```
body
└─ page-wrapper
└─ page-content
└─ section
└─ padding-global
└─ container / max-width
└─ block
```
### Why this matters
- Clear separation of concerns
- Consistent spacing and widths
- Easy to scan and reason about layouts
```html
```
---
## Sections
Sections group **major content areas** and control **vertical spacing**.
### When to use
* Every major page section
* Any time you need vertical rhythm between content groups
### Rules
* Sections own vertical spacing
* Use `.top-*` and `.bottom-*` classes
* Do not apply margins inside sections
```html
```
---
## Padding Global
`.padding-global` provides **consistent horizontal padding**.
### When to use
* Inside every section
* Whenever content needs safe spacing from screen edges
```html
```
Padding comes from `.padding-global`, **never from containers**.
---
## Containers
Containers constrain width and centre content in the viewport.
### When to use
* Most page content
* Any readable text or structured layout
### Key rules
* Containers centre content
* Containers do **not** add padding
* Use one container per section (in most cases)
```html
```
### Variants
| Class | Use |
| ------------------ | ---------------------- |
| `.container-small` | Narrow layouts |
| `.container-medium` | Default readable width |
| `.container-large` | Wider layouts |
---
## Max-Width
Max-width utilities apply **width limits only**.
### When to use
* Special cases
* Content that should not be centred
* Breaking out of container behaviour
| Class | Use |
| ------------------ | ----------------- |
| `.max-width-small` | Narrow constraint |
| `.max-width-medium` | Default readable |
| `.max-width-large` | Wide constraint |
| `.max-width-full` | No constraint |
**Rule of thumb**
* Centre content → use a container
* Only limit width → use max-width (exception, not default)
---
## Blocks
Blocks group **related content** and manage internal spacing.
### When to use
* Heading + text
* Text + buttons
* Image + caption
* Any content that belongs together
### Default behaviour
* Vertical flex layout
* Uses `gap` for spacing
* No margins on children
```html
```
### Gap modifiers
Item
Item
| Class | Effect |
| -------- | ----------- |
| `.gap-xs` | Very tight |
| `.gap-s` | Small |
| `.gap-m` | Default |
| `.gap-l` | Large |
| `.gap-xl` | Extra large |
```html
Heading
More space between elements
```
---
## Block Layout Modifiers
Blocks can change layout or alignment.
| Class | Effect |
| -------------- | ------------------- |
| `.row` | Horizontal layout |
| `.row-reverse` | Reversed horizontal |
| `.align-start` | Align items start |
| `.align-center` | Centre items |
| `.align-end` | Align items end |
```html
```
---
## Grid
Grids create **column-based layouts**.
They are not spacing utilities.
### Base grid
* Two equal columns
* Default gap applied
Column 2
```html
```
### Column modifiers
2
3
| Class | Columns |
| ----------- | ------------- |
| `.cols-3` | Three columns |
| `.cols-4` | Four columns |
```html
```
### Item width
| Class | Effect |
| ------------- | --------------------- |
| `.fit-content` | Item sizes to content |
```html
```
### Rules
* Use grids for layout, not spacing
* Use `gap-*` to adjust spacing
* Combine column + gap modifiers as needed
---
## CSS Conventions
## Overview
This guide ensures:
- Readable, modular, and maintainable CSS
- Clear organization with related styles grouped together
- Easy navigation and long-term maintenance
- Consistent commenting hierarchy
- Logical file structure
---
## Organisation Principles
### Group Related Styles Together
**Always keep related styles adjacent:**
- All font sizes together
- All spacing utilities together
- All color tokens of the same type together
- All modifiers for the same component together
- All hover/active states immediately after their base class
**Example - Typography Grouping:**
```css
/* -- Headings -- */
h1 {
font-size: var(--font-7xl);
line-height: var(--line-height-s);
font-weight: var(--font-weight-bold);
}
h2 {
font-size: var(--font-6xl);
line-height: var(--line-height-s);
font-weight: var(--font-weight-bold);
}
/* All headings grouped together */
```
**Example - Component Grouping:**
```css
/* -- Button -- */
.button, button {
padding: var(--space-s) var(--space-m);
background: var(--background-primary);
border: var(--border-s) solid var(--border-primary);
}
.button:hover {
background: var(--background-secondary);
}
.button.is-outline {
background: transparent;
}
/* All button-related styles together */
```
---
## Token Organisation
Use CSS custom properties (variables) for reusable values. Organize tokens logically:
**Example - Token Grouping:**
```css
:root {
/* -- Unit tokens -- */
--unit-s: 0.5rem; /* 8px */
--unit-m: 1rem; /* 16px */
/* -- Spacing scale -- */
--space-s: var(--unit-s);
--space-m: var(--unit-m);
/* -- Typography tokens -- */
--font-size-base: 1rem;
--font-weight-bold: 700;
}
```
**Principles:**
- Group tokens by purpose (units, spacing, typography, colors)
- Use subsections to organize within `:root`
- Reference tokens when possible (e.g., `--space-m: var(--unit-m)`)
- Keep related tokens adjacent
---
## Component Organization
When writing component styles, follow this order:
1. **Base class first**
2. **States immediately after base** (hover, active, focus)
3. **Modifiers grouped together** (is-*, has-*)
4. **Sub-components after modifiers**
**Example:**
```css
/* -- Card -- */
.card {
padding: var(--space-m);
background: var(--background-primary);
}
.card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card.is-compact {
padding: var(--space-s);
}
/* All card-related styles together */
```
---
## Property Ordering for Readability
Group related properties together within a selector:
**Suggested order:**
1. Layout (display, position, grid, flex)
2. Box model (width, height, margin, padding)
3. Visual (background, border, box-shadow)
4. Typography (font, line-height, text-align)
5. Other (opacity, cursor, transition)
**Example:**
```css
.button {
/* Layout */
display: inline-flex;
/* Box model */
padding: var(--space-m) var(--space-l);
/* Visual */
background: var(--background-primary);
border: var(--border-s) solid var(--border-primary);
/* Typography */
font-size: var(--font-m);
font-weight: var(--font-weight-semi-bold);
}
```
**Note**: The comments above are for demonstration only. In actual code, the visual grouping is what matters, not the comments.
---
## Commenting Hierarchy
Use **three levels only**. Do not invent new formats.
### 1. Major Sections
Used for top‑level structure of the file.
**Format**
```css
/* ------ Section Title ------ */
```
**Rules**
- Always uppercase
- One line only
- Blank line before and after
- Use to mark major divisions in your CSS
**Example**
```css
/* ------ BASE STYLES ------ */
body {
/* ... */
}
```
---
### 2. Subsections
Used to group related concepts inside a section.
**Format**
```css
/* -- Subsection name -- */
```
**Rules**
- Title case
- One line
- Use only when grouping related rules
- Blank line before
**Example**
```css
/* -- Headings -- */
h1 {
/* ... */
}
h2 {
/* ... */
}
```
---
### 3. Inline / Feature Comments
Used to clarify values or mark important behaviour.
**Format**
```css
/* Short clarification */
```
**Rules**
- Keep factual and brief
- Prefer same-line comments
- Do not explain obvious CSS
**Example**
```css
--unit-xs: 0.25rem; /* 4px */
```
---
## Responsive Code Organization
### Adding Breakpoints
When adding responsive breakpoints, follow these guidelines:
1. **Choose breakpoints that match your design** - Common breakpoints include:
- Mobile: `768px` (max-width)
- Tablet: `1024px` (max-width)
- Desktop: `1440px` (min-width)
2. **Keep breakpoints consistent** - Use the same breakpoints throughout your project
3. **Group responsive code together** - Place all media queries in a dedicated section at the end of your file
**Example:**
```css
/* ------ RESPONSIVE ------ */
/* Mobile */
@media (max-width: 768px) {
:root {
--font-size-base: 0.875rem;
}
.card {
padding: var(--space-s);
}
}
```
### Organization
When organizing responsive code:
1. **Override tokens in `:root`** - Don't override individual classes when possible
2. **Group by token type** - Fonts together, spacing together
3. **Use subsections** - Clearly mark what's being overridden
4. **Always at the end** - Responsive code should come last in the file
5. **Comment breakpoints** - Label each breakpoint clearly (Mobile, Tablet, Desktop)
**Example:**
```css
/* ------ RESPONSIVE ------ */
/* Mobile */
@media (max-width: 768px) {
:root {
/* -- Mobile font sizes -- */
--font-size-base: 0.875rem;
}
.card {
padding: var(--space-s);
}
}
```
### Best Practices
- Override tokens when possible, not individual classes
- Scale down proportionally
- Keep mobile values grouped by type
- Use clear subsection comments
---
## Rules to Follow
### Organization
- ✅ Group related styles together
- ✅ Keep all font-related styles in one place
- ✅ Keep all spacing utilities together
- ✅ Keep all color tokens of the same type together
- ✅ Place responsive code at the end
- ✅ Keep related properties together within selectors
### Commenting
- ✅ Use three-level commenting hierarchy
- ✅ Do not mix comment styles
- ✅ Do not skip levels
- ✅ Do not add decorative separators
- ✅ Prefer fewer comments over more
- ✅ Comments should help *find* things, not explain theory
### Code Quality
- ✅ Use design system tokens, over hardcode values
- ✅ Use semantic tokens over primitive tokens when possible
- ✅ Keep related properties together (e.g., all typography properties)
- ✅ Place states immediately after base classes
- ✅ Group modifiers together
### What to Avoid
- ❌ Scattering related styles across the file
- ❌ Mixing font sizes with spacing utilities
- ❌ Placing responsive code in the middle of sections
- ❌ Hardcoding values that should use tokens
- ❌ Using primitive tokens directly in layouts when semantic tokens exist
- ❌ Separating component states and modifiers from their base class
---
## Quick Reference
| Task | Rule |
|------|------|
| Add new token | Group with related tokens (colors with colors, spacing with spacing) |
| Add new utility class | Group with other utilities of the same type |
| Add new component | Group all related styles together (base, states, modifiers, sub-components) |
| Add responsive override | Place at end of file, override tokens when possible |
| Add modifier to component | Place immediately after component base class |
| Add state to component | Place immediately after base class, before modifiers |
---
## JavaScript Conventions
## Overview
This structure ensures readable, modular, and maintainable JavaScript with clear organization.
---
## File Structure
Every JavaScript file should follow this organizational pattern:
```js
/**
* Script Purpose: [Brief description]
* Author: [Your Name]
* Created: [Date]
* Version: [Latest Version]
* Last Updated: [Date]
*/
console.log("Script Name v1.0.0");
//
//------- Utility Functions -------//
//
// Function Name
function utilityFunction() {
// ... implementation
}
//
//------- Main Functions -------//
//
// Initialize Component
function initComponent() {
// ... implementation
}
//
//------- Event Listeners -------//
//
// Setup Event Listeners
function setupEventListeners() {
// ... implementation
}
//
//------- Initialize -------//
//
document.addEventListener("DOMContentLoaded", () => {
initComponent();
setupEventListeners();
});
```
---
## Section Organization
1. **File Header** - Comment block with purpose, author, dates, version
2. **Console Log** - ONE `console.log()` at the top to confirm script loaded
3. **Utility Functions** - Reusable helper functions grouped together
4. **Main Functions** - Primary functionality grouped together
5. **Event Listeners** - Event handling functions grouped together
6. **Initialize** - All setup calls within `DOMContentLoaded` listener at the end
---
## Section Dividers
Use clear headers when grouping similar functions:
```js
//
//------- Section Name -------//
//
```
For standalone functions, use simple comments:
```js
// Function Name
function doSomething() {
// ... implementation
}
```
---
## Naming Conventions
| Type | Example | Rule |
|------|---------|------|
| Functions | `initSlider()`, `handleClick()` | Use **lower camelCase**, short & descriptive |
| Variables | `mainElement`, `scrollPos` | Contextual and readable |
| File Names | `slider.js`, `utils.js` | Use lowercase kebab-case |
| Constants | `MAX_ITEMS`, `API_URL` | Use UPPER_SNAKE_CASE |
---
## Initialization Pattern
1. Define **utility functions** first
2. Define **main functions** grouped by section
3. Define **event listeners** separately
4. Call everything within a single `DOMContentLoaded` listener **at the end**
---
## Console Logging
**Each script should include ONE `console.log()` at the top** to confirm the script has loaded. Don't add excessive console logs throughout your code.
---
## Rules to Follow
### Organization
- ✅ Group related functions together
- ✅ Use section dividers for major groupings
- ✅ Place initialization at the end
- ✅ Keep utility functions separate from main functions
### Commenting
- ✅ Include file header with metadata
- ✅ Comment each function with its purpose
- ✅ Use section dividers for major groupings
### Code Quality
- ✅ Use descriptive function names
- ✅ Follow naming conventions consistently
- ✅ Place initialization in `DOMContentLoaded` listener
### What to Avoid
- ❌ Scattering related functions across the file
- ❌ Mixing utility functions with main functions
- ❌ Multiple initialization points
- ❌ Excessive console logging
---
## Design Tokens
These are the actual CSS custom property values from `design-system.css`. Always use semantic tokens (not primitives) in production code.
### 1. BRAND TOKENS
| Token | Value | Note |
| --- | --- | --- |
| **Typography** | | |
| `--font-primary` | `"Inclusive Sans", sans-serif` | |
| `--font-secondary` | `"RecifeText", Georgia, serif` | |
| `--font-tertiary` | `"Bugrino", sans-serif` | |
| `--font-quaternary` | `"IBM Plex Mono", monospace` | |
| **Palette** | | |
| `--off-white` | `var(--neutral-50)` | |
| `--warm-white` | `#f5ebe3` | |
| `--warm-black` | `#221f1c` | |
| `--off-black` | `var(--neutral-900)` | |
| **Accent colours** | | |
| `--red-lighter` | `#FFE8E3` | |
| `--red-light` | `#FFD6CD` | |
| `--red` | `#D92A27` | |
| `--red-dark` | `#750D0B` | |
| `--blue-lighter` | `#D5F3FF` | |
| `--blue-light` | `#B1E6FC` | |
| `--blue` | `#1A54D6` | |
| `--blue-dark` | `#152F57` | |
| `--yellow-lighter` | `#FFF3B8` | |
| `--yellow-light` | `#FFEA83` | |
| `--yellow` | `#FFB533` | |
| `--yellow-dark` | `#7E5700` | |
| `--green-lighter` | `#DBF7CC` | |
| `--green-light` | `#B6D6A5` | |
| `--green` | `#167255` | |
| `--green-dark` | `#094C45` | |
| `--purple-lighter` | `#F9E2FF` | |
| `--purple-light` | `#F5CDFF` | |
| `--purple` | `#AA4FE3` | |
| `--purple-dark` | `#600E83` | |
| **Base font sizes (rem-based, progressive step scale: +2px body, +4px heading, +8px display)** | | |
| `--font-3xs` | `0.625rem` | 10px |
| `--font-2xs` | `0.75rem` | 12px |
| `--font-xs` | `0.875rem` | 14px |
| `--font-s` | `1rem` | 16px |
| `--font-m` | `1.125rem` | 18px |
| `--font-l` | `1.25rem` | 20px |
| `--font-xl` | `1.375rem` | 22px |
| `--font-2xl` | `1.5rem` | 24px |
| `--font-3xl` | `1.75rem` | 28px |
| `--font-4xl` | `2rem` | 32px |
| `--font-5xl` | `2.25rem` | 36px |
| `--font-6xl` | `2.5rem` | 40px |
| `--font-7xl` | `3rem` | 48px |
| `--font-8xl` | `3.5rem` | 56px |
| `--font-9xl` | `4rem` | 64px |
| `--font-10xl` | `4.5rem` | 72px |
| **Semantic font sizes** | | |
| `--text-body` | `var(--font-m)` | |
| **UI font sizes** | | |
| `--button-font-size` | `var(--text-body)` | |
| `--button-font-size-small` | `var(--font-xs)` | |
| **Line height tokens** | | |
| `--line-height-xs` | `0.7` | |
| `--line-height-s` | `1` | |
| `--line-height-m` | `1.3` | |
| `--line-height-l` | `1.4` | |
| `--line-height-xl` | `1.6` | |
| `--line-height-2xl` | `1.8` | |
| **Font weight tokens** | | |
| `--font-weight-light` | `300` | |
| `--font-weight-regular` | `400` | |
| `--font-weight-medium` | `500` | |
| `--font-weight-semi-bold` | `600` | |
| `--font-weight-bold` | `700` | |
| `--font-weight-extra-bold` | `800` | |
| `--font-weight-black` | `900` | |
| **Letter spacing tokens (em-based for proportional scaling)** | | |
| `--letter-spacing-s` | `0.03em` | |
| `--letter-spacing-m` | `0.06em` | |
| `--letter-spacing-l` | `0.12em` | |
| `--letter-spacing-xl` | `0.24em` | |
### 2. SYSTEM TOKENS - COLORS
| Token | Value | Note |
| --- | --- | --- |
| **Neutrals** | | |
| `--neutral-50` | `#fafafa` | |
| `--neutral-100` | `#e5e5e5` | |
| `--neutral-150` | `#d4d4d4` | |
| `--neutral-200` | `#c4c4c4` | |
| `--neutral-300` | `#a3a3a3` | |
| `--neutral-400` | `#8a8a8a` | |
| `--neutral-500` | `#737373` | |
| `--neutral-600` | `#5c5c5c` | |
| `--neutral-700` | `#474747` | |
| `--neutral-800` | `#333333` | |
| `--neutral-900` | `#1f1f1f` | |
| `--neutral-950` | `#141414` | |
| `--neutral-990` | `#0a0a0a` | |
| **Black & White Alpha Tokens** | | |
| `--black` | `#000000` | |
| `--black-alpha-3` | `#00000008` | |
| `--black-alpha-5` | `#0000000d` | |
| `--black-alpha-10` | `#0000001a` | |
| `--black-alpha-15` | `#00000026` | |
| `--black-alpha-20` | `#00000033` | |
| `--black-alpha-30` | `#0000004d` | |
| `--black-alpha-40` | `#00000066` | |
| `--black-alpha-50` | `#00000080` | |
| `--black-alpha-60` | `#00000099` | |
| `--black-alpha-70` | `#000000b3` | |
| `--black-alpha-80` | `#000000cc` | |
| `--black-alpha-90` | `#000000e6` | |
| `--black-alpha-95` | `#000000f2` | |
| `--white` | `#ffffff` | |
| `--white-alpha-5` | `#ffffff0d` | |
| `--white-alpha-10` | `#ffffff1a` | |
| `--white-alpha-15` | `#ffffff26` | |
| `--white-alpha-20` | `#ffffff33` | |
| `--white-alpha-30` | `#ffffff4d` | |
| `--white-alpha-40` | `#ffffff66` | |
| `--white-alpha-50` | `#ffffff80` | |
| `--white-alpha-60` | `#ffffff99` | |
| `--white-alpha-70` | `#ffffffb3` | |
| `--white-alpha-80` | `#ffffffcc` | |
| `--white-alpha-90` | `#ffffffe6` | |
| `--white-alpha-95` | `#fffffff2` | |
| `--transparent` | `transparent` | |
| **Color-Mix Alpha Scale** | | |
| `--alpha-5` | `transparent 95%` | |
| `--alpha-10` | `transparent 90%` | |
| `--alpha-15` | `transparent 85%` | |
| `--alpha-20` | `transparent 80%` | |
| `--alpha-25` | `transparent 75%` | |
| `--alpha-30` | `transparent 70%` | |
| `--alpha-35` | `transparent 65%` | |
| `--alpha-40` | `transparent 60%` | |
| `--alpha-45` | `transparent 55%` | |
| `--alpha-50` | `transparent 50%` | |
| `--alpha-55` | `transparent 45%` | |
| `--alpha-60` | `transparent 40%` | |
| `--alpha-65` | `transparent 35%` | |
| `--alpha-70` | `transparent 30%` | |
| `--alpha-75` | `transparent 25%` | |
| `--alpha-80` | `transparent 20%` | |
| `--alpha-85` | `transparent 15%` | |
| `--alpha-90` | `transparent 10%` | |
| `--alpha-95` | `transparent 5%` | |
| **Semantic colours** | | |
| `--text-primary` | `var(--off-black)` | |
| `--text-secondary` | `var(--neutral-800)` | |
| `--text-plain` | `var(--black)` | |
| `--text-faded` | `var(--black-alpha-50)` | |
| `--text-accent` | `var(--blue)` | |
| `--text-link` | `var(--text-accent)` | |
| `--text-inverted` | `var(--off-white)` | |
| `--text-sidebar` | `var(--text-primary)` | |
| `--text-top-nav` | `var(--text-primary)` | |
| **Text with Opacity** | | |
| `--text-alpha-5` | `color-mix(in srgb, var(----text-primary), var(--alpha-5))` | |
| `--text-alpha-10` | `color-mix(in srgb, var(----text-primary), var(--alpha-10))` | |
| `--text-alpha-15` | `color-mix(in srgb, var(----text-primary), var(--alpha-15))` | |
| `--text-alpha-20` | `color-mix(in srgb, var(----text-primary), var(--alpha-20))` | |
| `--text-alpha-30` | `color-mix(in srgb, var(----text-primary), var(--alpha-30))` | |
| `--text-alpha-40` | `color-mix(in srgb, var(----text-primary), var(--alpha-40))` | |
| `--text-alpha-50` | `color-mix(in srgb, var(----text-primary), var(--alpha-50))` | |
| `--background-primary` | `var(--neutral-50)` | |
| `--background-secondary` | `var(--neutral-100)` | |
| `--background-plain` | `var(--white)` | |
| `--background-faded` | `var(--black-alpha-5)` | |
| `--background-darker` | `var(--black-alpha-10)` | |
| `--background-lighter` | `var(--white-alpha-10)` | |
| `--background-modal` | `rgba(0, 0, 0, 0.75)` | |
| `--background-sidebar` | `var(--background-primary)` | |
| `--background-top-nav` | `var(--background-primary)` | |
| **Solid colour backgrounds — for hero blocks, callouts, ad units, etc.** | | |
| `--background-accent` | `var(--text-accent)` | |
| `--background-black` | `var(--black)` | |
| `--background-white` | `var(--white)` | |
| `--background-blue` | `var(--blue)` | |
| `--background-red` | `var(--red)` | |
| `--background-green` | `var(--green)` | |
| `--border-primary` | `var(--text-primary)` | |
| `--border-secondary` | `var(--neutral-300)` | |
| `--border-faded` | `var(--black-alpha-15)` | |
| `--button-primary` | `var(--text-primary)` | |
| `--button-text` | `var(--off-white)` | |
| `--button-secondary` | `var(--black)` | |
| `--button-secondary-text` | `var(--off-white)` | |
| `--button-faded` | `color-mix(in srgb, var(--button-primary), var(--alpha-15))` | |
| **Status colours (from brand book or defaults)** | | |
| `--status-info` | `var(--blue)` | |
| `--status-info-bg` | `var(--blue-lighter)` | |
| `--status-success` | `var(--green)` | |
| `--status-success-bg` | `var(--green-lighter)` | |
| `--status-warning` | `var(--yellow-darker)` | |
| `--status-warning-bg` | `var(--yellow-lighter)` | |
| `--status-danger` | `var(--red)` | |
| `--status-danger-bg` | `var(--red-lighter)` | |
| `--status-accent` | `var(--purple)` | |
| `--status-accent-bg` | `var(--purple-lighter)` | |
| **Form semantic tokens** | | |
| `--input-border` | `var(--border-secondary)` | |
| `--input-background` | `var(--black-alpha-5)` | |
| `--input-text` | `var(--text-plain)` | |
| `--input-placeholder` | `var(--text-faded)` | |
| `--input-focus` | `var(--text-accent)` | |
| `--input-disabled-bg` | `var(--background-faded)` | |
| `--input-disabled-text` | `var(--text-faded)` | |
| `--checkbox-background` | `var(--neutral-100)` | |
| `--checkbox-selected` | `var(--text-primary)` | |
| `--checkbox-border` | `var(--border-faded)` | |
| `--checkbox-checkmark` | `var(--off-white)` | |
| `--toggle-track` | `var(--black-alpha-3)` | |
| `--toggle-knob` | `var(--neutral-500)` | |
| `--toggle-selected` | `var(--text-accent)` | |
| `--toggle-knob-selected` | `var(--off-white)` | |
| **Selection** | | |
| `--selection-text` | `var(--background-primary)` | |
| `--selection-background` | `var(--text-primary)` | |
| **Card tokens** | | |
| `--card-background` | `var(--background-primary)` | |
| `--card-border` | `var(--border-faded)` | |
| `--card-border-hover` | `var(--border-primary)` | |
| `--card-padding` | `var(--space-xl)` | |
| `--card-radius` | `var(--radius-m)` | |
| **Tooltip tokens** | | |
| `--tooltip-background` | `var(--warm-black)` | |
| `--tooltip-text` | `var(--off-white)` | |
| **Toast tokens** | | |
| `--toast-background` | `var(--warm-black)` | |
| `--toast-text` | `var(--off-white)` | |
| **Tab tokens** | | |
| `--tab-active-color` | `var(--text-primary)` | |
| `--tab-inactive-color` | `var(--text-faded)` | |
| `--tab-indicator-color` | `var(--text-primary)` | |
| **Progress tokens** | | |
| `--progress-track` | `var(--background-darker)` | |
| `--progress-fill` | `var(--text-primary)` | |
| **Divider tokens** | | |
| `--divider-color` | `var(--border-faded)` | |
| **Dropdown tokens** | | |
| `--dropdown-background` | `var(--background-primary)` | |
| `--dropdown-border` | `var(--border-faded)` | |
| `--dropdown-item-hover` | `var(--background-faded)` | |
| **Tag tokens** | | |
| `--tag-background` | `var(--background-darker)` | |
| `--tag-border` | `var(--border-faded)` | |
| **Dialog tokens** | | |
| `--dialog-background` | `var(--background-primary)` | |
| `--dialog-max-width` | `560px` | |
| `--dialog-shadow` | `0 8px 32px var(--black-alpha-20)` | |
| `--dialog-backdrop` | `var(--background-modal)` | |
| **Slider tokens** | | |
| `--slider-track-background` | `var(--background-darker)` | |
| `--slider-fill` | `var(--text-primary)` | |
| `--slider-thumb-background` | `var(--text-primary)` | |
| `--slider-thumb-border` | `var(--background-primary)` | |
| **Rating tokens** | | |
| `--rating-color` | `var(--yellow)` | |
| `--rating-color-empty` | `var(--background-faded)` | |
| `--rating-size` | `1.5rem` | |
| **Mark tokens** | | |
| `--mark-background` | `var(--yellow-light)` | |
| `--mark-text` | `var(--warm-black)` | |
### 2b. THEME TOKENS - DARK MODE
| Token | Value | Note |
| --- | --- | --- |
| **Unit tokens** | | |
| `--none` | `0` | |
| `--2xs` | `0.125rem` | |
| **2px** | | |
| `--xs` | `0.25rem` | |
| **4px** | | |
| `--s` | `0.5rem` | |
| **8px** | | |
| `--m` | `0.75rem` | |
| **12px** | | |
| `--l` | `1rem` | |
| **16px** | | |
| `--xl` | `1.5rem` | |
| **24px** | | |
| `--2xl` | `2rem` | |
| **32px** | | |
| `--3xl` | `2.5rem` | |
| **40px** | | |
| `--4xl` | `3rem` | |
| **48px** | | |
| `--5xl` | `3.5rem` | |
| **56px** | | |
| `--6xl` | `4rem` | |
| **64px** | | |
| `--7xl` | `4.5rem` | |
| **72px** | | |
| `--8xl` | `5rem` | |
| **80px** | | |
| `--9xl` | `5.5rem` | |
| **88px** | | |
| `--10xl` | `6rem` | |
| **96px** | | |
| `--11xl` | `6.5rem` | |
| **104px** | | |
| `--12xl` | `7rem` | |
| **112px** | | |
| `--13xl` | `7.5rem` | |
| **120px** | | |
| `--14xl` | `10rem` | |
| **Spacing scale** | | |
| `--space-none` | `var(--none)` | |
| `--space-2xs` | `var(--2xs)` | |
| `--space-xs` | `var(--xs)` | |
| `--space-s` | `var(--s)` | |
| `--space-m` | `var(--m)` | |
| `--space-l` | `var(--l)` | |
| `--space-xl` | `var(--xl)` | |
| `--space-2xl` | `var(--2xl)` | |
| `--space-3xl` | `var(--3xl)` | |
| `--space-4xl` | `var(--4xl)` | |
| `--space-5xl` | `var(--5xl)` | |
| `--space-6xl` | `var(--6xl)` | |
| `--space-7xl` | `var(--7xl)` | |
| `--space-8xl` | `var(--8xl)` | |
| `--space-9xl` | `var(--9xl)` | |
| `--space-10xl` | `var(--10xl)` | |
| `--space-11xl` | `var(--11xl)` | |
| `--space-12xl` | `var(--12xl)` | |
| `--space-13xl` | `var(--13xl)` | |
| `--space-14xl` | `var(--14xl)` | |
| **Section spacing variables** | | |
| `--section-xs` | `var(--space-xl)` | |
| `--section-s` | `var(--space-2xl)` | |
| `--section-m` | `var(--space-6xl)` | |
| `--section-l` | `var(--space-10xl)` | |
| `--section-xl` | `var(--space-14xl)` | |
| **Border width tokens** | | |
| `--border-s` | `1.5px` | |
| `--border-m` | `2px` | |
| `--border-l` | `4px` | |
| **Border composition variables** | | |
| `--border-width` | `var(--border-s)` | |
| `--border-style` | `solid` | |
| `--border-color` | `var(--border-primary)` | |
| **Border radius tokens** | | |
| `--radius-xs` | `4px` | |
| `--radius-s` | `6px` | |
| `--radius-m` | `10px` | |
| `--radius-l` | `16px` | |
| `--radius-xl` | `24px` | |
| `--radius-pill` | `999px` | |
### 4. SYSTEM TOKENS - MOTION
| Token | Value | Note |
| --- | --- | --- |
| **Easing primitives** | | |
| `--ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | |
| **fast start, slow end. for exits.** | | |
| `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` | |
| **slow start, slow end with long tail. for entrances.** | | |
| `--ease-in-out` | `cubic-bezier(0.65, 0, 0.35, 1)` | |
| **Duration primitives** | | |
| `--duration-2xs` | `100ms` | |
| `--duration-xs` | `200ms` | |
| `--duration-s` | `400ms` | |
| `--duration-m` | `600ms` | |
| `--duration-l` | `800ms` | |
| `--duration-xl` | `1200ms` | |
| `--duration-2xl` | `1500ms` | |
| **Page-level semantic motion** | | |
| `--motion-page-open-duration` | `var(--duration-xl)` | |
| **800ms** | | |
| `--motion-page-open-easing` | `var(--ease-in-out)` | |
| `--motion-page-close-duration` | `var(--duration-xl)` | |
| **600ms** | | |
| `--motion-page-close-easing` | `var(--ease-in-out)` | |
| `--motion-page-swap-duration` | `var(--duration-xl)` | |
| `--motion-page-swap-easing` | `var(--ease-in-out)` | |
| `--motion-page-fade-duration` | `var(--duration-m)` | |
| **400ms** | | |
| `--motion-page-fade-easing` | `var(--ease-in-out)` | |
### DARK MODE OVERRIDES
| Token | Value | Note |
| --- | --- | --- |
| **Text** | | |
| `--text-primary` | `#e8e6e3` | |
| `--text-secondary` | `#a8a5a2` | |
| `--text-plain` | `#f0eeeb` | |
| `--text-faded` | `rgba(255, 255, 255, 0.45)` | |
| `--text-accent` | `var(--yellow)` | |
| `--text-link` | `var(--text-accent)` | |
| `--text-inverted` | `#1a1a1a` | |
| `--text-sidebar` | `var(--text-primary)` | |
| `--text-top-nav` | `var(--text-primary)` | |
| **Background** | | |
| `--background-primary` | `var(--neutral-800)` | |
| `--background-secondary` | `#222222` | |
| `--background-plain` | `#2a2a2a` | |
| `--background-faded` | `rgba(255, 255, 255, 0.06)` | |
| `--background-darker` | `rgba(255, 255, 255, 0.12)` | |
| `--background-modal` | `var(--black-alpha-50)` | |
| `--background-sidebar` | `var(--background-primary)` | |
| `--background-top-nav` | `var(--background-primary)` | |
| **Border** | | |
| `--border-primary` | `#e8e6e3` | |
| `--border-secondary` | `#3a3a3a` | |
| `--border-faded` | `rgba(255, 255, 255, 0.12)` | |
| **Black & white alpha (inverted for dark surfaces)** | | |
| `--black-alpha-5` | `rgba(255, 255, 255, 0.05)` | |
| `--black-alpha-10` | `rgba(255, 255, 255, 0.08)` | |
| `--black-alpha-15` | `rgba(255, 255, 255, 0.12)` | |
| `--black-alpha-20` | `rgba(255, 255, 255, 0.16)` | |
| `--black-alpha-30` | `rgba(255, 255, 255, 0.22)` | |
| `--black-alpha-50` | `rgba(255, 255, 255, 0.45)` | |
| **Button** | | |
| `--button-primary` | `var(--text-primary)` | |
| `--button-text` | `var(--background-primary)` | |
| `--button-secondary` | `#e8e6e3` | |
| `--button-secondary-text` | `#1a1a1a` | |
| `--button-faded` | `color-mix(in srgb, var(--button-primary), var(--alpha-80))` | |
| **Status** | | |
| `--status-info` | `var(--blue-light)` | |
| `--status-success` | `var(--green-light)` | |
| `--status-warning` | `var(--yellow-light)` | |
| `--status-danger` | `var(--red-light)` | |
| `--status-accent` | `var(--purple-light)` | |
| **Form** | | |
| `--input-border` | `#3a3a3a` | |
| `--input-background` | `rgba(255, 255, 255, 0.08)` | |
| `--input-text` | `#f0eeeb` | |
| `--input-placeholder` | `rgba(255, 255, 255, 0.45)` | |
| `--input-focus` | `var(--text-accent)` | |
| `--input-disabled-bg` | `rgba(255, 255, 255, 0.06)` | |
| `--input-disabled-text` | `rgba(255, 255, 255, 0.3)` | |
| `--checkbox-background` | `#3a3a3a` | |
| `--checkbox-selected` | `#e8e6e3` | |
| `--checkbox-border` | `#555` | |
| `--checkbox-checkmark` | `#1a1a1a` | |
| `--toggle-track` | `#444` | |
| `--toggle-knob` | `#888` | |
| `--toggle-selected` | `#e8e6e3` | |
| `--toggle-knob-selected` | `#1a1a1a` | |
| **Card** | | |
| `--card-background` | `var(--background-secondary)` | |
| `--card-border` | `var(--border-faded)` | |
| `--card-border-hover` | `var(--border-primary)` | |
| **Tooltip / Toast** | | |
| `--tooltip-background` | `var(--neutral-100)` | |
| `--tooltip-text` | `var(--warm-black)` | |
| `--toast-background` | `var(--neutral-100)` | |
| `--toast-text` | `var(--warm-black)` | |
| **Dropdown** | | |
| `--dropdown-background` | `var(--background-plain)` | |
| **Dialog** | | |
| `--dialog-background` | `var(--background-secondary)` | |
| `--dialog-shadow` | `0 8px 40px var(--black-alpha-60)` | |
| **Mark** | | |
| `--mark-background` | `color-mix(in srgb, var(--yellow), var(--alpha-60))` | |
| `--mark-text` | `var(--off-white)` | |
---
## Color Usage
Color tokens define the shared, reusable color values that power both design and code. This page shows what each color looks like. For the full token list with values, see the [Tokens](tokens.html) page.
Colors are organised into two layers: **primitive tokens** (raw values) and **semantic tokens** (intent-based aliases). Always use semantic tokens in production code — primitives are the building blocks that semantic tokens reference.
---
## Brand Palette
The core brand colors that define the project identity. These are set per-project in the Brand Tokens section of `design-system.css`.
off-white Hex CSS
warm-white Hex CSS
warm-black Hex CSS
off-black Hex CSS
### Accent Colors
red-lighter Hex CSS
red-light Hex CSS
red Hex CSS
red-dark Hex CSS
blue-lighter Hex CSS
blue-light Hex CSS
blue Hex CSS
blue-dark Hex CSS
yellow-lighter Hex CSS
yellow-light Hex CSS
yellow Hex CSS
yellow-dark Hex CSS
green-lighter Hex CSS
green-light Hex CSS
green Hex CSS
green-dark Hex CSS
purple-lighter Hex CSS
purple-light Hex CSS
purple Hex CSS
purple-dark Hex CSS
---
## Primitive Scales
### Neutral
Warm grey ramp from lightest to near-black, used for backgrounds, borders, and secondary text.
neutral-50 Hex CSS
neutral-100 Hex CSS
neutral-150 Hex CSS
neutral-200 Hex CSS
neutral-300 Hex CSS
neutral-400 Hex CSS
neutral-500 Hex CSS
neutral-600 Hex CSS
neutral-700 Hex CSS
neutral-800 Hex CSS
neutral-900 Hex CSS
neutral-950 Hex CSS
neutral-990 Hex CSS
### Black Alpha
Semi-transparent black values for overlays, shadows, borders, and tints.
black Hex CSS
black-alpha-95 Hex CSS
black-alpha-90 Hex CSS
black-alpha-80 Hex CSS
black-alpha-70 Hex CSS
black-alpha-60 Hex CSS
black-alpha-50 Hex CSS
black-alpha-40 Hex CSS
black-alpha-30 Hex CSS
black-alpha-20 Hex CSS
black-alpha-15 Hex CSS
black-alpha-10 Hex CSS
black-alpha-5 Hex CSS
black-alpha-3 Hex CSS
### White Alpha
Semi-transparent white values for highlights and light overlays. Shown on a dark background for visibility.
white Hex CSS
white-alpha-95 Hex CSS
white-alpha-90 Hex CSS
white-alpha-80 Hex CSS
white-alpha-70 Hex CSS
white-alpha-60 Hex CSS
white-alpha-50 Hex CSS
white-alpha-40 Hex CSS
white-alpha-30 Hex CSS
white-alpha-20 Hex CSS
white-alpha-15 Hex CSS
white-alpha-10 Hex CSS
white-alpha-5 Hex CSS
---
## Semantic Colors
Semantic colors map primitive tokens to **meaning and intent**. Use these in layouts and components — never use primitives directly.
### Text
text-primary Hex CSS
text-secondary Hex CSS
text-plain Hex CSS
text-faded Hex CSS
text-accent Hex CSS
text-link Hex CSS
text-inverted Hex CSS
### Background
background-primary Hex CSS
background-secondary Hex CSS
background-plain Hex CSS
background-faded Hex CSS
background-darker Hex CSS
### Solid colour backgrounds
Solid colour blocks for hero sections, callouts, ad units, and any marketing surface that needs to lean on a brand colour. Each token resolves to the brand primitive when overridden in a client theme — otherwise the design system default applies.
background-accent Hex CSS
background-black Hex CSS
background-white Hex CSS
background-blue Hex CSS
background-red Hex CSS
background-green Hex CSS
### Border
border-primary Hex CSS
border-secondary Hex CSS
border-faded Hex CSS
### Button
button-primary Hex CSS
button-text Hex CSS
button-secondary Hex CSS
button-faded Hex CSS
### Status
Status tokens now reference the dark shade of each accent colour family. `var(--status-info)` maps to `var(--blue-dark)` (previously `var(--green)`), aligning with the conventional use of blue for informational states. In dark mode, status tokens flip to the light shades for readability.
status-info Hex CSS
status-success Hex CSS
status-warning Hex CSS
status-danger Hex CSS
status-accent Hex CSS
---
## Dark Mode
The design system uses a `data-theme` attribute to switch between light and dark modes. **Light mode is the default** — dark mode is an opt-in override, not a baseline. Activate it by setting `data-theme="dark"` on any element; tokens inherit through the cascade.
Dark mode values (e.g. `#1a1a1a`, `#e8e6e3`) are **overrides applied via `[data-theme="dark"]`** — never use them as primary values, and never hardcode them. If you're working from a dark-looking screenshot, confirm the intended theme before writing code.
### How It Works
- **`:root`** — light mode tokens (always present)
- **`[data-theme="dark"]`** — overrides semantic tokens with dark values
- **`@media (prefers-color-scheme: dark)`** — no-JS fallback for users with a dark OS preference
The toggle button in the site header sets `data-theme="dark"` on `` and persists the choice in `localStorage`.
### Scoped Usage
You can apply dark mode to any element, not just the page:
Dark mode
Primary text
Faded text
Accent text
```html
This section uses dark mode tokens
```
All semantic tokens inside that element resolve to their dark values via CSS custom property inheritance.
### Customizing Dark Mode
Edit the `[data-theme="dark"]` block in `design-system.css` (section 2b). When changing a value, also update the `@media (prefers-color-scheme: dark)` fallback (section 2c) to keep them in sync.
---
## Typography
Typography tokens provide a **consistent, modular system** for all text across the products. They are designed for **clarity, readability, and hierarchy**, while remaining flexible across devices.
The system uses three font families — a primary sans-serif for body and UI, a secondary serif for headings, and a tertiary monospace for code and labels. For the full list of typography token values, see the [Tokens](tokens.html) page.
---
## Font Sizes
The full type scale used across the system. Reference these tokens when setting font sizes on any element.
The scale uses a **progressive step** approach: +2px in the body range, +4px in the heading range, and +8px in the display range. This gives more granularity where it matters most.
| Token | Value | px Equivalent | Step |
| --- | --- | --- | --- |
| `var(--font-3xs)` | 0.625rem | 10px | — |
| `var(--font-2xs)` | 0.75rem | 12px | +2px |
| `var(--font-xs)` | 0.875rem | 14px | +2px |
| `var(--font-s)` | 1rem | 16px | +2px |
| `var(--font-m)` | 1.125rem | 18px | +2px |
| `var(--font-l)` | 1.25rem | 20px | +2px |
| `var(--font-xl)` | 1.375rem | 22px | +2px |
| `var(--font-2xl)` | 1.5rem | 24px | +2px |
| `var(--font-3xl)` | 1.75rem | 28px | +4px |
| `var(--font-4xl)` | 2rem | 32px | +4px |
| `var(--font-5xl)` | 2.25rem | 36px | +4px |
| `var(--font-6xl)` | 2.5rem | 40px | +4px |
| `var(--font-7xl)` | 3rem | 48px | +8px |
| `var(--font-8xl)` | 3.5rem | 56px | +8px |
| `var(--font-9xl)` | 4rem | 64px | +8px |
| `var(--font-10xl)` | 4.5rem | 72px | +8px |
---
## Headings
All headings use `var(--font-secondary)` (RecifeText) at `var(--font-weight-regular)` (400). Each level steps down in size to create a clear visual hierarchy.
Heading 2
Every detail contributes to the whole
Heading 3
Structure creates clarity in complexity
Heading 4
Good defaults eliminate guesswork
Heading 5
Constraints unlock creative freedom
Heading 6
Small decisions compound over time
```html
Build systems that scale with your ambition
Every detail contributes to the whole
Structure creates clarity in complexity
```
| Element | Token | px | Mobile (≤768px) | Line Height | Value |
| --- | --- | --- | --- | --- | --- |
| `h1` | `var(--font-7xl)` | 48px | `var(--font-5xl)` (36px) | `var(--line-height-m)` | 1.3 |
| `h2` | `var(--font-5xl)` | 36px | `var(--font-3xl)` (28px) | `var(--line-height-m)` | 1.3 |
| `h3` | `var(--font-3xl)` | 28px | — | `var(--line-height-m)` | 1.3 |
| `h4` | `var(--font-2xl)` | 24px | — | `var(--line-height-m)` | 1.3 |
| `h5` | `var(--font-xl)` | 22px | — | `var(--line-height-m)` | 1.3 |
| `h6` | `var(--font-xl)` | 22px | — | `var(--line-height-l)` | 1.4 |
Headings use `var(--line-height-m)` (1.3) so multi-line titles have breathing room. On mobile (≤768px), `h1` and `h2` step down a level to prevent giant titles from dominating small viewports.
---
## Body Text
The default paragraph style used for all running content. The `var(--text-body)` token controls the base size globally — changing it updates paragraphs, inputs, code, tables, and buttons at once. Size modifier classes let you step up or down from the default.
Large
Introductory text and section summaries that sit between headings and body text in the hierarchy.
Medium (default)
The default body text size. This is what you get without adding any size class — the baseline for all running content.
Small
Secondary content, supporting details, and supplementary information that doesn't need to compete with body text for attention.
Extra Small
Captions, footnotes, metadata, and fine print — available but not prominent.
```html
Lead paragraph text.
Introductory text.
Default body text (no class needed).
Smaller supporting text.
Captions and metadata.
```
| Element | Token | px | Line Height | Value |
| --- | --- | --- | --- | --- |
| `.text-size-xlarge` | `var(--font-3xl)` | 28px | `var(--line-height-l)` | 1.4 |
| `.text-size-large` | `var(--font-xl)` | 22px | `var(--line-height-l)` | 1.4 |
| `p` (default) | `var(--font-m)` | 18px | `var(--line-height-l)` | 1.4 |
| `.text-size-small` | `var(--font-s)` | 16px | `var(--line-height-xl)` | 1.6 |
| `.text-size-xsmall` | `var(--font-xs)` | 14px | `var(--line-height-xl)` | 1.6 |
---
## Eyebrow
A small, uppercase label used to provide context above headings, within sections, or inline with other content. The `.eyebrow` class works on any element — ``, ``, ``, or anything else.
Latest Update
New components added to the library
Section Label
An eyebrow on an h2 resets it to the small uppercase style, useful when you need heading semantics without heading size.
```html
Case Study
Building a design system from the ground up
Latest Update
Section Label
```
| Element | Token | Line Height | Value |
| --- | --- | --- | --- |
| `.eyebrow` | `var(--font-xs)` | `var(--line-height-m)` | 1.3 |
---
## Blockquote
Used for pullquotes and highlighted passages. Renders in the secondary serif font with a left border accent.
Long quote
A design system is more than a collection of components — it is a shared language between design and engineering. When done well, it reduces inconsistency, speeds up delivery, and creates a foundation that scales with the product.
```html
Good typography is invisible. Bad typography is everywhere.
```
| Element | Token | Line Height | Value |
| --- | --- | --- | --- |
| `blockquote` | `var(--font-m)` | `var(--line-height-l)` | 1.4 |
---
## Links
Links use text-color underlines with a hover transition. The default state shows `var(--text-plain)` text with a `var(--text-link)` coloured underline. On hover, the text colour shifts to `var(--text-link)` and the underline moves down slightly. External links (`target="_blank"`) automatically show a share icon via `::after` that inherits the link colour.
External link
Typography is powered by Google Fonts for web delivery.
```html
Read the full brand guidelines before starting.
Typography is powered by Google Fonts for web delivery.
```
| Property | Value |
| --- | --- |
| Default colour | `var(--text-plain)` |
| Underline colour | `var(--text-link)` |
| Hover colour | `var(--text-link)` |
| Underline offset | 2.5px → 4px on hover |
| Underline thickness | 1.5px |
| Transition | 0.3s all |
| External icon | `::after` on `a[target="_blank"]`, inherits `currentColor` |
---
## Font Families
The system uses four font stacks, each with a distinct role.
Secondary — RecifeText
Typography is the voice of design
Tertiary — Bugrino
Typography is the voice of design
Quaternary — IBM Plex Mono
Typography is the voice of design
```css
body { font-family: var(--font-primary); }
h1, h2, h3, h4, h5, h6 { font-family: var(--font-secondary); }
code, pre, kbd { font-family: var(--font-quaternary); }
```
| Token | Font | Used For |
| --- | --- | --- |
| `var(--font-primary)` | Inclusive Sans | Body text, UI, labels |
| `var(--font-secondary)` | RecifeText | Headings, blockquotes |
| `var(--font-tertiary)` | Bugrino | Brand display, eyebrows, buttons, badges |
| `var(--font-quaternary)` | IBM Plex Mono | Code, pre, kbd |
---
## Font Weight
Available weight values from light to black. All headings default to regular weight.
Regular
Systems scale when decisions are shared
Medium
Tokens turn intention into consistency
Semi-Bold
Structure creates clarity in complexity
Bold
Good defaults eliminate guesswork
Extra-Bold
Constraints unlock creative freedom
Black
Build with purpose, not by accident
```css
font-weight: var(--font-weight-bold);
```
| Token | Value |
| --- | --- |
| `var(--font-weight-light)` | 300 |
| `var(--font-weight-regular)` | 400 |
| `var(--font-weight-medium)` | 500 |
| `var(--font-weight-semi-bold)` | 600 |
| `var(--font-weight-bold)` | 700 |
| `var(--font-weight-extra-bold)` | 800 |
| `var(--font-weight-black)` | 900 |
---
## Line Height
Vertical rhythm values from tight display text to loose body copy. Headings use tighter values; body text uses looser values for readability.
Small
Design tokens capture color, typography, spacing, and border values as reusable variables so that design and code stay in sync across every surface.
Medium
Design tokens capture color, typography, spacing, and border values as reusable variables so that design and code stay in sync across every surface.
Large
Design tokens capture color, typography, spacing, and border values as reusable variables so that design and code stay in sync across every surface.
Extra Large
Design tokens capture color, typography, spacing, and border values as reusable variables so that design and code stay in sync across every surface.
2X Large
Design tokens capture color, typography, spacing, and border values as reusable variables so that design and code stay in sync across every surface.
```css
line-height: var(--line-height-l);
```
| Token | Value | Used For |
| --- | --- | --- |
| `var(--line-height-xs)` | 0.7 | Tight display text |
| `var(--line-height-s)` | 1 | Tight display text |
| `var(--line-height-m)` | 1.3 | Headings, eyebrows |
| `var(--line-height-l)` | 1.4 | Body text, paragraphs |
| `var(--line-height-xl)` | 1.6 | Small text, captions |
| `var(--line-height-2xl)` | 1.8 | Spacious body text |
---
## Letter Spacing
Tracking values used for labels, eyebrows, and display text. Values are em-based so they scale proportionally with font size.
Medium
Design System Components
Large
Design System Components
Extra Large
Design System Components
```css
letter-spacing: var(--letter-spacing-xl);
```
| Token | Value | Used For |
| --- | --- | --- |
| `var(--letter-spacing-s)` | 0.03em | Subtle tracking |
| `var(--letter-spacing-m)` | 0.06em | Medium tracking |
| `var(--letter-spacing-l)` | 0.12em | Wide tracking |
| `var(--letter-spacing-xl)` | 0.24em | Eyebrows, labels |
---
## Spacing
Spacing tokens define **distance**, not intent. They are reused for gaps, padding, and margins depending on context. For the full list of spacing token values, see the [Tokens](tokens.html) page.
The system is built in layers: **unit tokens** (raw values) → **space tokens** (semantic aliases) → **utility classes** (applied in HTML). Always use space tokens or utility classes — never hardcode pixel values.
---
## Space Scale
The space scale provides a visual reference for the spacing tokens used throughout the system.
| Token | Value | px Equivalent |
| --- | --- | --- |
| `var(--space-none)` | 0 | 0px |
| `var(--space-2xs)` | 0.125rem | 2px |
| `var(--space-xs)` | 0.25rem | 4px |
| `var(--space-s)` | 0.5rem | 8px |
| `var(--space-m)` | 0.75rem | 12px |
| `var(--space-l)` | 1rem | 16px |
| `var(--space-xl)` | 1.5rem | 24px |
| `var(--space-2xl)` | 2rem | 32px |
| `var(--space-3xl)` | 2.5rem | 40px |
| `var(--space-4xl)` | 3rem | 48px |
| `var(--space-5xl)` | 3.5rem | 56px |
| `var(--space-6xl)` | 4rem | 64px |
| `var(--space-7xl)` | 4.5rem | 72px |
| `var(--space-8xl)` | 5rem | 80px |
| `var(--space-9xl)` | 5.5rem | 88px |
| `var(--space-10xl)` | 6rem | 96px |
```css
padding: var(--space-m);
gap: var(--space-xl);
margin-bottom: var(--space-l);
```
---
## Gap
Gap modifiers control the space between child elements inside a `.block`. The default gap is `var(--space-m)`.
Second item
Third item
Extra Small
First item
Second item
Third item
Small
First item
Second item
Third item
Default gap
First item
Second item
Third item
Large
First item
Second item
Third item
Extra Large
First item
Second item
Third item
```html
Item one
Item two
Item three
```
| Class | Value | px Equivalent |
| --- | --- | --- |
| `.gap-none` | 0 | 0px |
| `.gap-xs` | `var(--space-xs)` | 4px |
| `.gap-s` | `var(--space-s)` | 8px |
| `.gap-m` | `var(--space-m)` | 12px |
| `.gap-l` | `var(--space-l)` | 16px |
| `.gap-xl` | `var(--space-xl)` | 24px |
| `.gap-2xl` | `var(--space-2xl)` | 32px |
| `.gap-3xl` | `var(--space-3xl)` | 40px |
---
## Padding
Padding utilities apply internal spacing to an element on all sides.
```html
```
| Class | Value | px Equivalent |
| --- | --- | --- |
| `.padding-s` | `var(--space-s)` | 8px |
| `.padding-m` | `var(--space-m)` | 12px |
| `.padding-l` | `var(--space-l)` | 16px |
| `.padding-xl` | `var(--space-xl)` | 24px |
| `.padding-2xl` | `var(--space-2xl)` | 32px |
| `.padding-3xl` | `var(--space-3xl)` | 40px |
---
## Section Spacing
Section spacing controls the vertical rhythm between major page sections. Apply `.top-*` and `.bottom-*` classes to `` elements. These scale responsively between desktop and mobile.
```html
```
| Class | Token | Desktop | Mobile |
| --- | --- | --- | --- |
| `.top-small` / `.bottom-small` | `var(--section-s)` | 32px | 24px |
| `.top-medium` / `.bottom-medium` | `var(--section-m)` | 64px | 32px |
| `.top-large` / `.bottom-large` | `var(--section-l)` | 96px | 56px |
| `.top-xl` / `.bottom-xl` | `var(--section-xl)` | 160px | 80px |
---
## Border System
Borders use a **composable architecture** that separates positioning from styling. Structural classes define where the border appears, and combo classes modify width, style, and color independently. For border token values, see the [Tokens](tokens.html) page.
---
## Structure
Structural classes define **where** the border appears. By default, borders use `var(--border-s)` width, solid style, and `var(--border-primary)` color.
Top
Content with top border
Bottom
Content with bottom border
Left
Content with left border
Right
Content with right border
```html
All sides
Top only
Bottom only
Left only
Right only
```
| Class | Effect |
| --- | --- |
| `.border` | Border on all sides |
| `.border-top` | Top only |
| `.border-bottom` | Bottom only |
| `.border-left` | Left only |
| `.border-right` | Right only |
---
## Width
Width classes modify the border thickness. The default is `var(--border-s)`.
```html
Medium border on all sides
```
| Class | Token | px Equivalent |
| --- | --- | --- |
| `.border-s` | `var(--border-s)` | 1.5px |
| `.border-m` | `var(--border-m)` | 2px |
| `.border-l` | `var(--border-l)` | 4px |
---
## Style
Style classes modify the border appearance. The default is solid.
```html
Dashed border
```
| Class | Style |
| --- | --- |
| `.border-solid` | Solid (default) |
| `.border-dashed` | Dashed |
| `.border-dotted` | Dotted |
---
## Color
Color classes modify the border color using semantic tokens.
Secondary
Medium, neutral border
Faded
Subtle, light border
```html
Subtle border
```
| Class | Token |
| --- | --- |
| `.border-primary` | `var(--border-primary)` |
| `.border-secondary` | `var(--border-secondary)` |
| `.border-faded` | `var(--border-faded)` |
---
## Radius
Border radius tokens control corner rounding. Apply them directly via CSS — there are no utility classes for radius.
```css
border-radius: var(--radius-m);
```
| Token | Value |
| --- | --- |
| `var(--radius-xs)` | 4px |
| `var(--radius-s)` | 6px |
| `var(--radius-m)` | 10px |
| `var(--radius-l)` | 16px |
| `var(--radius-xl)` | 24px |
| `var(--radius-pill)` | 999px |
---
## Composing Borders
Combine structural, width, style, and color classes to build any border you need. Each class modifies a single concern.
Bottom + large + primary
Composed border
All sides + faded + dotted
Composed border
```html
Composed: top + medium + dashed + secondary
```
---
## Component Conventions
This document defines the rules and conventions for building components in the By Default design system. All contributors must follow these patterns to keep the system consistent and predictable.
---
## How this system is organized
Components are split across **four layers** so the design system can be lifted into other products without dragging the BrandOS docs site along with it:
- **`foundation`** — tokens, layout primitives, utilities. Lives in `assets/css/design-system.css`. Ships with every product.
- **`core`** — reusable components (button, card, dropdown, …) plus brand identity docs (`brand-*.md`). Lives in `design-system.css`. Ships with every product.
- **`docs-site`** — components that only power *this* BrandOS docs site (asset-card, book-cover, dont-card, sticky-bar, copy-button). Lives in `assets/css/docs-site.css`. Does **not** ship.
- **`app`** — BrandOS-specific tools, integrations, and project content (calculators, world clock, ad preview, project case studies). Does **not** ship.
Every `cms/*.md` doc declares its layer in frontmatter:
```yaml
---
title: "Button"
section: "Design System"
layer: "core"
---
```
The `layer` field is **required** — the doc generator validates it on every build. See CLAUDE.md §17 (Layer Discipline) for the seven rules that govern this split.
---
## Naming convention
- **Base class:** `.component-name` (e.g. `.badge`, `.card`, `.toast`)
- **Modifiers:** `.component-name--modifier` (e.g. `.badge--success`, `.card--flush`)
- **State classes:** `.is-state` (e.g. `.is-active`, `.is-open`, `.is-disabled`, `.is-hidden`, `.is-loading`) — shared across components
- **Utility overrides:** use `!important` only on utility classes (e.g. `.gap-m`)
- **JS hooks:** use `data-*` attributes, never CSS class names
**Legacy note:** The button component uses `.is-outline`, `.is-faded`, `.is-small`, `.is-xsmall`, `.is-icon` as modifiers. These predate the `var(--modifier)` convention and are kept for backward compatibility. New components must use `var(--modifier)` syntax.
---
## Token rule
Every visual value in component CSS must reference a CSS custom property defined in `:root`. Never hardcode hex values, pixel values (except structural ones like `border-radius: 50%`), or raw font values.
Component tokens follow this pattern:
```css
--component-property: var(--semantic-token);
```
Example:
```css
--card-background: var(--background-primary);
--card-border: var(--border-faded);
--card-radius: var(--radius-m);
--card-padding: var(--space-xl);
```
---
## File rule
| What | Where |
|------|-------|
| Component CSS | `assets/css/design-system.css` under a numbered section heading |
| Component JS (if needed) | `assets/js/component-name.js` |
| Documentation source | `cms/component-name.md` |
| Generated docs page | `design-system/component-name.html` |
Section headings in `design-system.css` follow the format:
```css
/* ------ 16. BADGE ------ */
```
---
## Accessibility rule
Every interactive component must include:
| Requirement | Details |
|-------------|---------|
| ARIA roles | Correct `role` attribute (e.g. `role="tablist"`, `role="tab"`, `role="tabpanel"`) |
| ARIA attributes | `aria-selected`, `aria-controls`, `aria-labelledby`, `aria-current`, `aria-label` as needed |
| Keyboard support | Tab to focus, Enter/Space to activate, Escape to dismiss (where applicable), Arrow keys for navigation (tabs, menus) |
| Focus indicator | `box-shadow: 0 0 0 2px color-mix(in srgb, var(--input-focus), transparent 75%)` |
| Screen reader text | Use `aria-label` or visually hidden text for icon-only actions |
---
## Component status
| Component | CSS class | Needs JS | Docs page |
|-----------|-----------|----------|-----------|
| Button | `.button` | No | `button.md` |
| Form elements | `.form-group`, `.form-check`, `.form-toggle`, `.segmented-control` | No | `form.md` |
| Callout | `.callout` | No | `callout.md` |
| Disclosure | `details`/`summary` | No | `disclosure.md` |
| Badge | `.badge` | No | `badge.md` |
| Card | `.card` | No | `card.md` |
| Breadcrumb | `.breadcrumb` | No | `breadcrumb.md` |
| Tabs | `.tabs`, `.tab` | Yes (`tabs.js`) | `tabs.md` |
| Progress | `.progress-bar` | No | `progress.md` |
| Tooltip | `[data-tooltip]` | No | `tooltip.md` |
| Toast | `.toast` | Yes (`toast.js`) | `toast.md` |
| Code / Pre / Kbd | `code`, `pre`, `kbd` | No | `code.md` |
| Mark / Abbr / Figure | `mark`, `abbr`, `figure` | No | `mark.md` |
> Asset Card, Book Cover, Don't Card, Sticky Bar and Copy Button are documented separately as **docs-site components** — they only exist to power this BrandOS docs site and are not part of the portable design system. See [Layer Discipline](../docs/setup.html) and CLAUDE.md §17.
---
## Dark mode rule
Components must **not** contain dark-mode-specific CSS. They rely entirely on semantic token overrides in `[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`.
The only exception is when a component uses brand-palette tokens directly (avoid this). If unavoidable, add the override to both the `[data-theme="dark"]` block and the `@media` fallback block.
Current exceptions:
- `mark` element — uses `var(--yellow-light)` via `var(--mark-background)` token, requires dark mode override
- Scrollbar — uses neutral scale tokens directly, requires dark mode override
---
## How to add a new component
1. **Define tokens** in `:root` (in `design-system.css`, after existing component tokens):
```css
/* -- Component tokens -- */
--component-property: var(--semantic-token);
```
2. **Add dark mode overrides** if the component uses non-semantic tokens — add to both `[data-theme="dark"]` and `@media (prefers-color-scheme: dark)` blocks.
3. **Write CSS** in `design-system.css` under a new numbered section:
```css
/* ------ N. COMPONENT NAME ------ */
```
4. **Write JS** (only if needed) in `assets/js/component-name.js`. Follow the existing pattern: IIFE, named functions, version logged to console.
5. **Write documentation** in `cms/component-name.md` following the standard frontmatter and content structure.
6. **Update this spec file** — add the component to the status table above.
7. **Regenerate docs:**
```bash
cd cms/generator && npm run docgen
```
---
## Components
### Button
Buttons are interactive elements used to trigger actions. They size to their content by default and should communicate **clear intent and hierarchy**.
The `.button` class is required for styled buttons. The bare `` element has only a minimal reset — always add `class="button"` to get the full button appearance.
---
## Primary Button
The default `.button` is the most prominent action on the page.
```html
Primary Action
Primary Action
```
---
## Outline Button
`.is-outline` removes the filled background and uses a border instead. Use for secondary actions that shouldn't compete with the primary CTA.
Disabled
Secondary Action
```html
Secondary Action
```
---
## Faded Button
`.is-faded` applies a subtle background for low-priority or passive actions.
```html
Optional Action
```
---
## Outline + Faded
`.is-outline.is-faded` combines both modifiers for tertiary or utility actions.
```html
Tertiary Action
```
---
## Small Button
`.is-small` reduces font size and padding for dense UI areas.
Disabled
Small Primary
Small Outline
```html
Small Primary
Small Outline
Small Faded
```
---
## Icon Button
`.is-icon` creates a circular button designed for icons only. Always include `aria-label` for accessibility.
```html
```
---
## Button Group
`.button-group` is a flex container for grouping multiple buttons together. It provides consistent spacing, wraps on smaller screens, and vertically centres buttons of different sizes.
Right-aligned
Cancel
Confirm
Mixed sizes
Primary
Small Secondary
Small Tertiary
```html
Confirm
Cancel
...
...
```
| Class | Effect |
| --- | --- |
| `.button-group` | Flex container with consistent gap |
| `.justify-center` | Centre-aligns the group |
| `.justify-end` | Right-aligns the group |
---
## All Variants
A side-by-side comparison of every button style at default size.