
Typeui Fundamentals
- 1.1k installs
- 1.7k repo stars
- Updated July 4, 2026
- bergside/typeui
TypeUI Fundamentals is a skill providing universal, design-system-agnostic UI/UX principles for hierarchy, interaction, typography, and WCAG accessibility.
About
TypeUI Fundamentals is a design-principles skill that supplies universal, design-system-agnostic UI/UX guidance covering visual hierarchy, interaction laws, typography, and WCAG accessibility. It defines a load order and conflict-resolution rules so an agent falls back to these principles when a specific design system is silent. A developer uses it to validate design decisions and enforce accessibility across any surface.
- Universal, design-system-agnostic UI/UX principles
- Five modules: ui, spacing, ux (30 laws), typography, accessibility
- Resolves conflicts between aesthetics and non-negotiable accessibility
Typeui Fundamentals by the numbers
- 1,094 all-time installs (skills.sh)
- +99 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #368 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
typeui-fundamentals capabilities & compatibility
- Capabilities
- ui principles · accessibility check · typography guidance
- Use cases
- ui design · web design
- Pricing
- Free
What typeui-fundamentals says it does
Universal UI/UX design principles covering visual hierarchy, interaction laws, typography foundations, and WCAG accessibility requirements.
These are timeless, design-system-agnostic foundations that apply to every surface regardless of vertical or workflow phase.
Accessibility is non-negotiable at every level — it overrides aesthetic preferences everywhere.
npx skills add https://github.com/bergside/typeui --skill typeui-fundamentalsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 1.7k |
| Last updated | July 4, 2026 |
| Repository | bergside/typeui ↗ |
How do agents apply universal UI design principles?
Apply universal UI/UX and WCAG accessibility principles when a specific design system does not cover a design decision.
Who is it for?
Resolving design decisions and enforcing accessibility when a design system is silent.
Skip if: Concrete token values or component specs, which the design system should define.
When should I use this skill?
You must make or validate a design decision not covered by a specific design system.
What you get
Principle-compliant layout decisions, accessibility-checked UI guidance, and conflict-resolved design rationale across five fundamentals modules.
- Principle-compliant UI guidance
- Accessibility-checked design decisions
- Conflict-resolution notes for generated layouts
By the numbers
- 30 UX laws in ux-principles.md
- 9 control states
- 5 principle modules
Files
Design Fundamentals — Agent Instructions
Universal design principles that define why patterns work, how to apply them correctly, and what accessibility requirements are non-negotiable. These are timeless, design-system-agnostic foundations that apply to every surface regardless of vertical or workflow phase.
Load order
Read these files after loading the design system (SKILL.md / DESIGN.md + token modules). The design system tells you what token to use; these files explain why that decision is correct and how to avoid common principle violations.
Module index
| File | Purpose |
|---|---|
| ui-principles.md | Universal visual design principles — hierarchy, layout rhythm, typography placement, color theory, depth & layering, interaction design, responsive adaptation, component behavior |
| spacing-principles.md | Spacing-specific principles — 4-point grid, proximity grouping, inner vs outer gaps, vertical and horizontal rhythm, heading→paragraph spacing, section and component spacing tiers |
| ux-principles.md | Interaction & control principles — 30 UX laws, button/control state contracts (9 states), hover/active technique palettes, touch targets, cognitive load, feedback loops |
| typography-principles.md | Typography-specific principles — type system foundations, scale & modular ratios, readability & measure, accessibility, responsive type, brand tone expression through type |
| accessibility.md | WCAG 2.1/2.2 compliance — contrast ratios, color-as-information rules, focus visibility, keyboard navigation, motion safety, target sizes, text spacing, semantic structure, ARIA |
What these files are NOT
These files do not define:
- Workflow phases (think → build → check → ship) — see
skills/vertical/SKILL.md - Quality gates or audits — see
skills/vertical/inspect.md,review.md,preflight.md - Anti-pattern catalogs — see
skills/vertical/anti-patterns.md - Industry-specific content — see
skills/vertical/team-social-saas.md - Design tokens or component specs — see
skills/design-system/SKILL.md
Conflict resolution
When sources disagree:
1. Design system wins for concrete values (colors, sizes, spacing tokens, component specs). 2. Fundamentals (this layer) win for structural principles (hierarchy, accessibility, motion logic). 3. Vertical wins for process decisions AND content architecture (section order, required sections, industry tone).
Accessibility is non-negotiable at every level — it overrides aesthetic preferences everywhere.
When the design system is silent, these principles decide
Any design decision not covered by the system's tokens or component rules falls back to the principles here. Never contradict the design system — if a principle and a design-system rule conflict, the design system wins. Flag the conflict for review.
Accessibility Principles — Color, Contrast, Perception & Interaction
Agent-ready accessibility rules derived from WCAG 2.1/2.2 (Level AA baseline, AAA where noted), covering contrast calculation, color accessibility, focus management, keyboard interaction, motion safety, text resizing, and target sizing. Every rule is testable. Every threshold is a number, not an adjective.
---
0. How agents must use this document
1. Accessibility overrides everything. When an accessibility rule conflicts with a visual preference or a brand guideline — accessibility wins. Always. Redesign the visual, not the constraint. 2. Preserve the design system palette. This file is design-system-agnostic. When a color token fails a contrast threshold, the agent must NOT replace it with an unrelated color or introduce hues outside the existing palette. Instead, adjust the failing token along its own hue/saturation axis — darken or lighten the same color family until it passes. The goal is to improve the design system's own colors, not override them. If a warm brand color fails as text on a light background, the fix is a deeper variant of that same warm color — not an unrelated hue, not grey, not an arbitrary hex. The palette's identity, warmth, and character must survive the accessibility fix. 3. Test every color pairing. Before shipping any text/background combination, compute the contrast ratio. "It looks fine" is not a measurement. 4. Apply rules during generation, not after. Building accessible and then auditing is cheaper than building pretty and then retrofitting. Load this file alongside the design system. 5. AA is the floor, not the ceiling. Every rule below states the AA minimum. Where AAA thresholds exist, they are noted as aspirational targets. 6. Disabled is not exempt from perception. WCAG exempts inactive components from contrast minimums, but users must still perceive the component exists and understand why it's unavailable. A disabled button nobody can see is a missing feature, not an accessible one.
---
1. Contrast — The Math
1.1 The formula
Contrast ratio = (L1 + 0.05) / (L2 + 0.05)
L1= relative luminance of the lighter colorL2= relative luminance of the darker color- Result ranges from
1:1(identical) to21:1(black on white)
1.2 Relative luminance
For sRGB (the web's default colorspace):
L = 0.2126 × R + 0.7152 × G + 0.0722 × BWhere R, G, B are linearized:
if sRGB_channel ≤ 0.04045:
linear = sRGB_channel / 12.92
else:
linear = ((sRGB_channel + 0.055) / 1.055) ^ 2.4And sRGB_channel = 8bit_value / 255.
1.3 Why this matters for agents
Do not eyeball contrast. Compute it. The human eye is unreliable for contrast judgment, especially across hues. A vibrant orange text on a warm cream background feels readable but may fail the 4.5:1 threshold. Run the math or use a tool.
Agent rule: For every color + background-color pair in the output, compute the contrast ratio before shipping. If you cannot compute it, flag the pair for manual verification.
---
2. Text Contrast — SC 1.4.3 (AA) and SC 1.4.6 (AAA)
2.1 Minimum ratios
| Text type | AA minimum | AAA target | How to measure |
|---|---|---|---|
| Normal text (< 18pt regular, < 14pt bold) | 4.5 : 1 | 7 : 1 | Foreground color vs. background color |
| Large text (≥ 18pt regular / 24px, or ≥ 14pt bold / 18.5px) | 3 : 1 | 4.5 : 1 | Foreground color vs. background color |
| Incidental text (inactive UI, pure decoration, invisible, part of a photo) | No requirement | No requirement | — |
| Logotype text (part of a logo or brand name) | No requirement | No requirement | — |
2.2 What counts as "large text"
- 18pt regular = 24px at standard density
- 14pt bold = 18.5px bold at standard density
- The conversion:
1pt = 1.333px - For unusual or thin-stroke fonts, the threshold may need to be higher — thin strokes reduce effective contrast even when the color ratio passes.
2.3 Agent rules — text contrast
- DO verify every text/background pair against the table above.
- DO test on the actual background, including gradients, images, and patterns. Use the worst-case pixel when text overlays an image.
- DO add a scrim, text-shadow, or solid background when placing text over images to guarantee contrast at the worst-case point.
- DO treat placeholder text and tooltip text as real text — they need contrast too.
- DO use the foreground and background colors from the CSS, not the rendered pixel colors (anti-aliasing can shift perceived contrast).
- DO NOT round contrast ratios favorably —
4.499:1fails the4.5:1threshold. - DO NOT assume dark mode passes because light mode did. Recompute every pairing in both themes.
- DO NOT use pure white (#FFFFFF) on pure black (#000000) for body text — it causes halation (text appears to vibrate) for some users. Prefer near-white (#E6E6E6 – #F5F5F5) on near-black (#0A0A0A – #1A1A1A).
2.4 Common failure modes
| Failure | Why it fails | Fix |
|---|---|---|
| Light gray text on white | Ratio drops below 4.5:1 | Darken the text or darken the background |
| Brand-color text on a tinted background of the same hue | Same hue = low luminance difference | Shift the background to a contrasting hue or use a neutral |
| Text over a photograph | Contrast varies across the image | Add a semi-transparent overlay behind the text |
| Thin-weight font at small size | Strokes are too thin to maintain perceived contrast | Use Regular (400) weight minimum, or increase the contrast ratio beyond 4.5:1 |
| Specifying text color without background (or vice versa) | The user's default may not contrast with your color | Always specify both foreground and background |
---
3. Non-Text Contrast — SC 1.4.11 (AA)
3.1 What it covers
Visual information required to identify:
- User interface components: buttons, inputs, checkboxes, toggles, sliders, tabs — the visual boundary or indicator that identifies the component.
- Component states: focus rings, selected indicators, active states, error borders — the visual change that communicates the current state.
- Graphical objects: icons, charts, diagrams, infographics — any part of a graphic required to understand the content.
3.2 Minimum ratio
3 : 1 against adjacent colors.
3.3 What is exempt
- Inactive (disabled) components where the appearance is determined by the user agent and not modified by the author.
- Graphics where a particular presentation is essential (a flag, a photograph, a heat map with a legend).
3.4 Agent rules — non-text contrast
- DO ensure every button border, input border, checkbox outline, toggle track, and slider thumb has ≥ 3:1 contrast against the adjacent surface.
- DO ensure focus indicators have ≥ 3:1 contrast against both the component and the page background.
- DO ensure icon strokes used for meaning (not decoration) have ≥ 3:1 contrast.
- DO ensure chart elements (bars, lines, pie segments) have ≥ 3:1 contrast against adjacent elements and the background.
- DO NOT rely on the CSS border alone — if the border is the same color as the background, the component boundary is invisible.
- DO NOT use subtle hover-only color changes as the sole affordance — they may fall below 3:1 on some monitors.
---
4. Color as Information — SC 1.4.1 (A)
4.1 The rule
Color must never be the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
This does not discourage the use of color — color is a powerful design asset. It only requires that color is never the sole channel.
4.2 The lightness loophole (and its limit)
WCAG acknowledges one nuance: if two colors differ not only in hue but also in lightness with a contrast ratio of ≥ 3:1 between them, the lightness difference counts as an additional visual distinction. Example: light green (#6BA51F) vs. dark red (#8F2E00) differ in both hue and luminance — if their mutual contrast ≥ 3:1, they pass.
However, if the user must identify a specific color (not just "lighter vs. darker"), an additional non-color indicator is always required regardless of contrast. Example: "green means valid, red means invalid" fails even at 10:1 contrast between the two colors — because a protanopic user cannot distinguish which color they are seeing. Add an icon (✓ / ✕) or text label.
Agent rule: The lightness loophole is a minimum, not a design goal. Always provide a redundant channel. Use the 3:1 inter-color contrast as a safety net, not a strategy.
4.3 What this means in practice
| Situation | Color alone (fails) | Color + another channel (passes) |
|---|---|---|
| Error state | Red border on input | Red border + error icon + error text message |
| Required field | Red asterisk | Red asterisk + "(required)" label text |
| Link in body text | Blue text, no underline | Blue text + underline (or underline on hover + different weight) |
| Chart series | Different-colored lines | Different colors + different dash patterns + labels |
| Status badge | Green = success, red = error | Color + icon (✓, ✕) + label text ("Passed", "Failed") |
| Selected state | Blue background | Blue background + checkmark icon + aria-selected |
| Navigation current page | Bold + brand color | Bold + brand color + underline or sidebar indicator |
| Form validation | Green outline = valid, red outline = invalid | Color + icon inside the field + helper text below |
| Data visualization | Color-coded regions on a map | Color + pattern fills + text labels + legend with shapes |
4.4 Links deserve special attention
Links embedded in body text are the most common failure of SC 1.4.1. If the only visual difference between a link and surrounding text is color, users with color vision deficiency cannot find the link.
Minimum: Link text must have ≥ 3:1 contrast against surrounding non-link text, AND provide an additional visual cue on hover/focus (underline, bold, icon).
Best practice: Always underline body-text links. text-decoration: underline is the universal affordance. Reserve "no underline" for links that are already visually distinct by structure (navigation bars, card titles, buttons).
Hard rule — underlines by context:
- Inline content links (inside paragraphs, list items, table cells): MUST have
text-decoration: underlineat rest. This is the primary affordance that makes them discoverable. - Navigation links (inside
<nav>,<header>, or navigation components): MUST NOT have underlines. Their structural position (grouped in a bar, sidebar, or menu) already communicates they are links. Underlines add visual noise without aiding discoverability. - Footer links (inside
<footer>): MUST NOT have underlines. Same reasoning — position within a clearly-labelled navigation column is the affordance. - Button-role links (any
<a>with.btnclass or button-like styling): MUST NOT have underlines. The button shape/fill is the affordance. - In CSS: Set
nav a, footer a, header a:not(.btn) { text-decoration: none; color: inherit; }. Apply underlines only top a,li a,td a, and similar inline-content contexts.
4.5 Visited links (exemption)
Authors have almost no control over visited-link styling due to browser privacy restrictions (:visited only allows color changes, and browsers restrict querying visited state). For this reason, WCAG does not require authors to distinguish visited from unvisited links through non-color means. Color-only distinction between visited and unvisited links is not a failure of SC 1.4.1.
The author must still ensure all link text (visited or not) meets contrast minimums against the page background per SC 1.4.3.
4.6 Documented WCAG failure patterns
These are officially catalogued failures — treat them as hard rules:
| Failure ID | Description | Fix |
|---|---|---|
| F13 | Image's text alternative omits information conveyed by color differences in the image | Include color-conveyed info in the alt text or provide a long description |
| F73 | Links not visually evident without color vision (no underline, no icon, no weight change) | Add underline or other non-color visual cue to links |
| F81 | Required or error fields identified by color differences only (red label, no icon/text) | Add "(required)" text or an error icon alongside the color change |
4.7 Agent rules — color as information
- DO pair every color-encoded meaning with at least one of: icon, text label, pattern, shape, position, weight change, underline.
- DO ensure any two colors used to encode different meanings have ≥ 3:1 contrast between each other (the lightness safety net).
- DO underline links in body text — or provide ≥ 3:1 contrast against surrounding text plus a non-color hover/focus cue.
- DO ensure navigation and footer links inherit their color from the parent container (not from a global link style) so they meet contrast against their actual background. A footer on a dark background needs white or light-colored links, not the body-text brand color.
- DO test with a color-blindness simulator (protanopia, deuteranopia, tritanopia) — if the meaning disappears, it relied on color alone.
- DO include color-conveyed information in image alt text (e.g., "pie chart: 60% blue = returning users, 40% orange = new users").
- DO NOT use red/green as the only distinction between success and error — even at high contrast.
- DO NOT identify required fields or errors by color alone — always add text or an icon.
- DO NOT assume "it's obvious from context" — always provide a redundant channel.
- DO NOT confuse "sufficient contrast between two colors" with "accessible" — a protanopic user may still see both colors as the same hue.
---
5. Focus Visibility — SC 2.4.7 (AA) and SC 2.4.11/2.4.13 (WCAG 2.2)
5.1 The rule
Any keyboard-operable interface must have a visible focus indicator. WCAG 2.2 adds:
- SC 2.4.11 (Focus Not Obscured — Minimum): The focused component is not entirely hidden by author-created content.
- SC 2.4.13 (Focus Appearance): The focus indicator has sufficient size and contrast.
5.2 Focus indicator requirements
| Property | Minimum |
|---|---|
| Thickness | ≥ 2px outline or equivalent |
| Offset | ≥ 2px from the component edge (so it doesn't overlap content) |
| Contrast | ≥ 3:1 against the adjacent background and the component's own surface |
| Visibility | Must not be obscured by sticky headers, footers, overlays, or tooltips |
| Animation | None — focus ring must appear instantly (0ms transition) |
5.3 Agent rules — focus
- DO use
:focus-visible(not bare:focus) so mouse clicks don't show the ring, but keyboard navigation does. - DO maintain a consistent focus ring across all interactive elements — focus is system feedback, not brand expression.
- DO test by tabbing through the entire page — every interactive element must show a visible indicator.
- DO ensure the focus ring survives when the element has hover, active, or selected styles applied.
- DO NOT use
outline: noneoroutline: 0without providing a custom replacement. This is the single most common accessibility violation. - DO NOT animate the focus ring's appearance — users need instant orientation.
- DO NOT lose focus on re-render, route change, or modal open/close — restore focus programmatically.
---
6. Keyboard Accessibility — SC 2.1.1 / 2.1.2 (A)
6.1 The rules
- All functionality must be operable through a keyboard interface.
- No keyboard traps — if focus can enter a component, it must be able to leave via keyboard.
6.2 Agent rules — keyboard
- DO ensure every interactive element is reachable via Tab / Shift+Tab.
- DO ensure buttons activate on Space and Enter; links activate on Enter.
- DO maintain logical tab order that matches the visual reading order.
- DO provide keyboard alternatives for any mouse-only interaction (drag-and-drop, hover menus, scroll-to-reveal).
- DO trap focus inside modals and dialogs (this is the correct use of a focus trap — focus stays in the dialog until it's dismissed, then returns to the trigger).
- DO NOT use
tabindexvalues > 0 (they create unpredictable tab order). - DO NOT rely on hover states for critical information — hover doesn't exist on keyboard or touch.
- DO NOT use
pointer-events: noneas a substitute fordisabled— it hides the element from keyboard and assistive tech.
---
7. Motion and Animation Safety — SC 2.3.1 (A) / 2.3.3 (AAA)
7.1 The rules
- No more than 3 flashes per second in any area of the page — flashing can trigger seizures.
- Animation from interactions must be disableable (WCAG 2.2).
- Moving, blinking, or scrolling content that starts automatically and lasts > 5 seconds must have a pause/stop/hide mechanism (SC 2.2.2).
7.2 Agent rules — motion
- DO respect
prefers-reduced-motion: reducefor all animations, transitions, and parallax effects. - DO provide a static end-state for every animation — the content must be fully usable without motion.
- DO limit entrance animations to one orchestrated sequence per page load (brand surfaces only); product surfaces get no entrance choreography.
- DO keep state transitions between 100–250ms with
ease-outeasing. - DO allow users to pause auto-playing carousels, marquees, and any auto-scrolling content.
- DO NOT use
animation-iteration-count: infiniteon content-bearing elements without a pause mechanism. - DO NOT flash or strobe any element — even once per second is risky for photosensitive users.
- DO NOT use parallax scrolling without a reduced-motion fallback.
- DO NOT animate text content — body copy must be readable at rest.
---
8. Text Resizing — SC 1.4.4 (AA)
8.1 The rule
Text must be resizable up to 200% without loss of content or functionality, without requiring assistive technology (i.e., using only browser zoom).
8.2 Agent rules — resizing
- DO use
remoremfor font sizes — they scale with the user's browser font-size preference. - DO test layouts at 200% browser zoom on a 1280px viewport — content must not overflow, overlap, or disappear.
- DO use
clamp()for fluid typography so text scales smoothly between viewport sizes. - DO NOT use
pxfor body text font sizes — they don't scale with browser preferences. - DO NOT disable pinch-to-zoom on mobile (
<meta name="viewport" content="... maximum-scale=1.0">is a violation). - DO NOT set
max-widthon containers usingpxvalues that prevent content from reflowing at 200% zoom.
---
9. Reflow — SC 1.4.10 (AA)
9.1 The rule
Content must be presentable without two-dimensional scrolling at:
- 320 CSS px width for vertical-scrolling content (equivalent to 1280px at 400% zoom)
- 256 CSS px height for horizontal-scrolling content
Exceptions: data tables, maps, diagrams, video, and interfaces requiring two-dimensional layout.
9.2 Agent rules — reflow
- DO design mobile-first — if it works at 320px, it almost always works at larger sizes.
- DO test at 320px viewport width — no horizontal scrollbar should appear on content.
- DO allow images to shrink (use
max-width: 100%andheight: auto). - DO NOT use fixed-width layouts that force horizontal scrolling on narrow viewports.
- DO NOT clip or hide content at narrow widths — reflowed content must remain accessible.
---
10. Target Size — SC 2.5.5 (AAA) / SC 2.5.8 (AA, WCAG 2.2)
10.1 The thresholds
| Standard | Minimum target size | Notes |
|---|---|---|
| WCAG 2.2 SC 2.5.8 (AA) | 24 × 24 px | Absolute floor — hard fail below this |
| Apple HIG | 44 × 44 px | Recommended for all touch targets |
| Material Design | 48 × 48 dp | Recommended for all touch targets |
| Best practice | 44 × 44 px minimum with ≥ 8px spacing | What this standards system requires |
10.2 Agent rules — target size
- DO size all buttons, links, checkboxes, radio buttons, and other interactive elements at ≥ 44 × 44px on touch devices.
- DO add ≥ 8px spacing between adjacent interactive targets to prevent mis-taps.
- DO use padding (not just text size) to meet the target minimum — a 14px link with no padding is an inaccessible target.
- DO NOT place destructive actions adjacent to primary actions without sufficient spacing.
- DO NOT rely on icon-only buttons smaller than 24px without expanding the hit area via padding.
---
11. Text Spacing — SC 1.4.12 (AA)
11.1 The rule
Content must remain functional when the user overrides text spacing to:
- Line height ≥ 1.5× the font size
- Paragraph spacing ≥ 2× the font size
- Letter spacing ≥ 0.12× the font size
- Word spacing ≥ 0.16× the font size
11.2 Agent rules — text spacing
- DO use relative units for spacing (
em,rem,%) so user overrides scale proportionally. - DO test with a "text spacing override" bookmarklet or browser extension — content must not clip, overlap, or disappear.
- DO NOT set fixed
heighton text containers — when line-height increases, the container must grow. - DO NOT use
overflow: hiddenon text containers without verifying behavior under increased spacing.
---
12. Content on Hover or Focus — SC 1.4.13 (AA)
12.1 The rule
When hover or focus triggers additional content (tooltips, dropdowns, popovers):
- Dismissible: the user can dismiss the content without moving the pointer or focus (typically via Escape).
- Hoverable: the user can move the pointer over the additional content without it disappearing.
- Persistent: the content stays visible until the user dismisses it, the trigger loses hover/focus, or the information is no longer valid.
12.2 Agent rules — hover/focus content
- DO keep tooltips visible when the user moves the pointer into the tooltip area.
- DO allow Escape to dismiss any hover/focus-triggered content.
- DO ensure hover-triggered content does not obscure the trigger element.
- DO NOT use
mouseoutto immediately hide a tooltip — add a delay or keep it visible while the pointer is in the tooltip area. - DO NOT trigger content on hover that cannot be triggered on focus (keyboard parity).
---
13. Semantic Structure — SC 1.3.1 (A) / SC 2.4.6 (AA) / SC 2.4.10 (AAA)
13.1 The rules
- Info and relationships conveyed through presentation must also be programmatically determinable (SC 1.3.1).
- Headings and labels must describe topic or purpose (SC 2.4.6).
- Section headings should be used to organize content (SC 2.4.10).
13.2 Agent rules — structure
- DO use real
<h1>–<h6>elements for headings, not styled<div>s or<span>s. - DO use one
<h1>per page. Never skip heading levels (<h2>→<h4>with no<h3>). - DO use
<nav>,<main>,<header>,<footer>,<aside>, and<section>landmark elements. - DO label landmark regions when there are multiples (e.g., two
<nav>elements — label themaria-label="Main"andaria-label="Footer"). - DO use
<button>for actions,<a>for navigation — never swap them. - DO use
<label>associated with every form<input>—aria-labelis a fallback, not a replacement. - DO NOT use
<div>withrole="button"when a real<button>element works. - DO NOT break words across inline elements (
<span>Wel</span><span>come</span>) — screen readers read this as two separate words.
---
14. Skip Navigation — SC 2.4.1 (A)
14.1 The rule
A mechanism must be available to bypass blocks of content repeated on multiple pages (navigation, headers, sidebars).
14.2 Implementation
<a href="#main-content" class="sr-only focus:not-sr-only ...">
Skip to main content
</a>- The link is visually hidden but appears on keyboard focus.
- The target (
#main-content) must be the<main>element or equivalent.
14.3 Agent rules — skip nav
- DO include a skip-to-content link as the first focusable element on every page.
- DO make it visible on
:focusso keyboard users can see and use it. - DO NOT hide it with
display: noneorvisibility: hidden— those remove it from the focus order entirely.
---
15. Design System Contrast Audit Checklist
Run this checklist against every design system palette and every page surface.
Text on backgrounds
- [ ] Body text on primary background — ≥ 4.5:1
- [ ] Body text on secondary background — ≥ 4.5:1
- [ ] Body text on brand/accent background — ≥ 4.5:1
- [ ] Heading text on primary background — ≥ 3:1 (large text) or ≥ 4.5:1 (normal)
- [ ] Muted/subtle text on all backgrounds — ≥ 4.5:1
- [ ] Link text on all backgrounds — ≥ 4.5:1
- [ ] Placeholder text — ≥ 4.5:1 (it is real text the user reads)
- [ ] Disabled text — ≥ 3:1 (perceivable, even if exempt from AA)
- [ ] Text on dark-mode surfaces — recomputed, not assumed
Interactive elements
- [ ] Button label on button fill — ≥ 4.5:1
- [ ] Button border against adjacent background — ≥ 3:1
- [ ] Input border against background — ≥ 3:1
- [ ] Focus ring against component surface — ≥ 3:1
- [ ] Focus ring against page background — ≥ 3:1
- [ ] Selected state indicator — ≥ 3:1
- [ ] Error state border/icon — ≥ 3:1
- [ ] Hover state change — perceptible (not required to be ≥ 3:1 on its own, but the hover state itself must still meet text contrast)
Graphical elements
- [ ] Icon strokes conveying meaning — ≥ 3:1
- [ ] Chart data elements against adjacent elements — ≥ 3:1
- [ ] Informational SVG strokes/fills — ≥ 3:1
---
16. Conflict Resolution
When accessibility rules conflict with other principles:
1. Accessibility wins over aesthetics. A beautiful button with 3:1 text contrast is a broken button. Fix the contrast, redesign the beauty. 2. Accessibility wins over brand — but within the palette. A brand color that fails contrast cannot be used as-is on text. The fix is to shift that color along its own hue axis (darker or lighter variant) until it passes — not to replace it with an unrelated hue. If no same-hue variant can pass, move the failing color to a decorative role (background, accent, illustration) and use a palette-consistent alternative for the text. 3. Accessibility wins over consistency — improve the token, don't abandon it. If the design system specifies a muted text color that fails 4.5:1, the token has a bug. Darken or lighten it within its color family until it passes. The palette's character must survive the fix. 4. Accessibility wins over density. If meeting target sizes (44×44px) requires more spacing, the spacing wins. Density is a preference; accessibility is a right. 5. Never introduce foreign colors. This file is agnostic to any specific design system. Agents must read the design system's color definitions first, then apply accessibility rules as constraints on top of that palette — never bypassing it. Every contrast fix must produce a color that belongs to the same family as the original token.
---
Spacing Principles
The reasoning layer for whitespace, proximity, and rhythm — how to choose gaps, padding, and margins when building sections, components, and full pages. Written so AI agents can generate, audit, or improve layout spacing regardless of framework or stack. Apply these principles after the active design system's spacing tokens and alongsideui-principles.mdandtypography-principles.md.
---
0. How agents must use this file
1. Load and apply the design system first — always. Spacing tokens, component padding specs, section rhythm, and container rules in the design-system files (e.g. layout.md, component modules) are the source of truth for concrete pixel values. 2. Then apply these spacing principles to decide which token belongs where — inner-group vs between-group, vertical vs horizontal, tight vs loose — when the brief or component spec is silent. 3. Never contradict the design system. If a principle here and a design-system token conflict, the design system wins. Flag the conflict for human review. 4. Never invent arbitrary values. Pick from the allowed scale (see §1). If the design system defines an 8px base unit, all spacing must be multiples of that base — which also satisfies a 4-point grid.
Application order (mandatory)
1. Design system spacing tokens & component specs → ship these first
2. Spacing principles (this file) → choose tier, direction, grouping
3. UI principles + typography principles → refine hierarchy, polish, edge cases---
1. The spacing scale
4-point grid (universal math)
The 4-point spacing system uses multiples of 4px for every margin, padding, and gap: 4, 8, 12, 16, 20, 24, 32, 48, 64, 96…
Benefits:
- Consistency — one rhythm across pages, sections, and components
- Developer speed — finite set of values, predictable handoff
- Visual hierarchy — different multiples signal different levels of importance
Map to your design system
Many design systems anchor on 8px (or 16px) as the primary step. That is compatible with the 4-point grid — 8, 16, 24, 32, 48, 96 are all valid 4-point values.
| Role | Typical values (4pt grid) | Example design-system mapping |
|---|---|---|
| Micro — icon↔label inside a control | 4px, 8px | Icon gap, badge icon margin |
| Tight — within a group | 8px, 12px | List item internals, label→field |
| Default — related siblings | 16px | Button groups, form fields in a stack, flex row gap |
| Medium — between subgroups | 24px, 32px | Card grid gap, card internal sections |
| Loose — between major groups | 48px, 64px | Section header → content |
| Section — page rhythm | 96px+ (desktop), 24px+ (mobile) | Section vertical padding |
Rule: use at least three distinct spacing tiers in every layout (small / medium / large). If every gap is the same, hierarchy collapses and users cannot scan the structure.
---
2. Core principle: proximity creates grouping
Spacing is the primary grouping tool — stronger than borders for most content.
- Tight spacing → "these belong together"
- Loose spacing → "this is a new group, section, or topic"
Inner vs outer (non-negotiable)
Padding around a group must be larger than spacing within the group.
When elements are wrapped in a container (card, toolbar, form section, icon cluster):
outer padding (group edge → first child) > inner gap (child ↔ child)If inner and outer gaps are equal, the group does not read as a unit. This applies vertically and horizontally — including icon rows, button clusters, and nav utility bars.
Start generous, then tighten
When spacing feels wrong, do not add the minimum gap and increment until it "works" — that produces cramped layouts.
Preferred workflow: 1. Start with more whitespace than you think you need 2. Remove space until the grouping still reads clearly 3. Verify three tiers (tight / default / loose) are visibly distinct
---
3. Vertical spacing
3.1 Typography rhythm
| Element | Line-height guidance | Notes |
|---|---|---|
| Body copy | ~1.5× font size | Comfortable reading; increase 2–3px if the typeface defaults feel tight |
| Subheadings | ~1.3× | Between body and display |
| Headings / titles | ~1.2× | Tighter — do not use body line-height on large headings |
| Between paragraphs | ≈ one body font-size | Use paragraph spacing, not blank lines / <br> stacks |
Heading → paragraph spacing (non-negotiable)
- When a heading is immediately followed by a paragraph (lead copy, body text, or description block), the heading must have 32px margin-bottom.
- Applies to every heading level (
h1–h6), section titles, block titles, and card headings when the next element is paragraph text — not a list, form field, button, or image. - Use `margin-bottom: 32px` on the heading (or the design-system token mapped to 32px). Do not use default browser heading margins, arbitrary values, or padding on the paragraph to fake the gap.
- Implementation: set margin-bottom on the heading, not margin-top on the paragraph — the heading owns the space to its content below.
- This 32px gap binds the heading to the copy that follows it. It must still be looser than paragraph→paragraph spacing only when body copy uses a smaller gap (typically 16px) — never equal or tighter than the gap between two body paragraphs.
Heading-to-section must remain looser than heading-to-paragraph (e.g. 32px heading→body vs 48–96px section rhythm). The heading belongs to the content below it, not the block above.
3.2 Lists
When a list item contains multiple lines or sub-elements (title + meta, label + value):
- Use two spacing tiers inside the item — tight between related lines, default between distinct data points
- Use looser spacing between list items than within an item
- Do not apply one uniform margin to every line — users cannot tell which data belongs together
3.3 Forms and inputs
The most common spacing failure: equal gaps everywhere.
❌ label ──16px── field ──16px── label ──16px── field
(user cannot tell which label owns which field)
✅ label ──8px── field ──24px── label ──8px── field
(tight label↔field = one unit; loose field↔next label = new unit)| Relationship | Tier | Typical token |
|---|---|---|
| Heading → paragraph | Fixed | 32px margin-bottom on the heading |
| Label → its input | Tight | 4–8px |
| Input → next label (or next field group) | Loose | 16–24px |
| Section title → first field | Default–medium | 16–32px |
Apply the same logic to search bars, filter rows, and settings panels. See also ui-principles.md §0.1 — Icon inset on inputs must balance both sides.
3.4 Sections and pages
| Relationship | Tier | Principle |
|---|---|---|
| Heading → paragraph | Fixed | 32px margin-bottom on the heading |
| Heading → lead paragraph | Same as above | 32px — heading binds to copy below |
| Paragraph → paragraph | Default | ≈ body font-size (typically 16px) |
| Content block → content block | Medium | 24–32px |
| Section header area → section content | Medium–loose | 48–64px |
| Section → section | Section | Equal top/bottom padding per design system |
Sections must share equal vertical padding top and bottom — do not compress one edge to "save space."
---
4. Horizontal spacing
4.1 Inside components
| Pattern | Typical gap | Example |
|---|---|---|
| Icon ↔ label (button, link, input) | 8px | "Talk to sales" + phone icon |
| Inline text + chevron (dropdown trigger) | 8px | "Products ▾" |
| Horizontal padding in inputs/buttons | Often ≈ font size | 16px padding at 16px type — easy to remember and scan |
Icon-to-edge and icon-to-text balance on inputs is defined in ui-principles.md — both gaps must match.
4.2 Between sibling components
| Pattern | Tier | Example |
|---|---|---|
| Related controls in one row | Default (16px) | Login + Sign up, picker + divider + link |
| Icon-only controls within a cluster | Micro (4px) | Theme + notification + menu toggle |
| Icon-only cluster ↔ text-labeled control | Default (16px) | Icon group → Logout link |
| Unrelated columns or regions | Medium–loose | Brand ↔ nav ↔ utilities |
4.3 Groups laid out horizontally
Same rule as vertical: space within the group < space around the group.
❌ [ icon 16px icon 16px icon ] 16px Logout
(icons feel disconnected; group boundary is weak)
✅ [ icon 4px icon 4px icon ] 16px Logout
(icons read as one utility cluster)Cards in a row, social icons, toolbar actions, and nav utility bars all follow this pattern.
---
5. Margin vs padding
| Property | What it controls | Use for |
|---|---|---|
| Padding | Space inside an element's border | Breathing room around text/icons inside a button, card, or input |
| Margin | Space outside an element's border | Separating siblings, pushing a block away from neighbors |
Rules:
- Use padding to size a component's internal comfort — never rely on margin to fake internal spacing inside a bordered surface
- Use margin or parent gap (
flex-gap,grid-gap) to separate siblings — prefergapon flex/grid parents over chaining margins on children - Collapsing margins cause surprises — in modern layouts, prefer gap on the parent
---
6. Spacing by surface type
Cards
| Zone | Tier | Guidance |
|---|---|---|
| Card padding (edge → content) | Default–medium | 16–24px |
| Image → title | Tight–default | 12–16px |
| Title → body paragraph | Fixed | 32px margin-bottom on the title |
| Body → footer / CTA | Default | 16px |
| Card ↔ card in a grid | Medium | 24–32px |
Button rows
- Adjacent buttons: same height and padding — see
ui-principles.md§0.1 - Gap between labeled buttons: 16px (default tier)
- Gap between icon-only buttons in a cluster: 4px
- Gap between icon cluster and labeled button: 16px
- Buttons use the design-system base size — do not shrink buttons to match compact inputs
Navigation bars
- Primary link lists: 24px gap (32px from medium breakpoint up) unless the brief specifies otherwise
- Utility/action rows: apply inner vs outer and icon-group rules from §4
- See
prompts-pro/marketing-navbars.mdfor variant-specific measurements
Modals, drawers, sidebars
- Outer padding: default–medium (16–24px)
- Title → body: default (16–24px)
- Stacked form fields: follow §3.3 tier rules
- Footer action row: 16px between buttons; flush to modal padding on sides
---
7. Responsive spacing
- Section padding scales down on mobile — keep top and bottom equal at each breakpoint
- Do not proportionally shrink every gap on mobile — preserve tier ratios (tight still tighter than loose)
- Touch targets need adequate padding inside and ≥ 8px between adjacent interactive targets
- Horizontal container padding may decrease on small screens — internal component spacing tiers stay the same
---
8. Agent rules — spacing
DO
- DO pick every spacing value from the 4-point / design-system scale — no
13px,17px,22px - DO use at least three tiers (tight / default / loose) in every section and component
- DO keep inner-group spacing tighter than between-group spacing
- DO keep group outer padding larger than inner gaps
- DO apply 32px margin-bottom on every heading immediately followed by a paragraph
- DO bind labels to their fields with tight spacing; separate field groups with loose spacing
- DO start with generous whitespace and remove until grouping still reads
- DO use
gapon flex/grid parents for sibling spacing - DO match heading line-height to role (tighter for large headings, looser for body)
- DO keep section vertical padding symmetric (top = bottom)
DO NOT
- DO NOT use default browser heading margins instead of the 32px heading→paragraph gap
- DO NOT use equal spacing between every element on a page
- DO NOT use blank lines,
<br>tags, or empty divs instead of defined paragraph/section spacing - DO NOT give a list item one uniform margin for all internal lines when structure varies
- DO NOT space icon-only controls at 16px from each other when they form one cluster
- DO NOT override design-system section or container tokens with ad-hoc values
- DO NOT add borders or dividers when proximity already communicates the grouping
---
9. Quick audit checklist
Before shipping a section, component, or page:
1. Scale — Are all spacing values multiples of 4 (and of the design-system base)? 2. Tiers — Can you name the tight, default, and loose gaps used? Are they visibly different? 3. Grouping — Does every label sit closer to its field than to the next field? 4. Headings — Does every heading followed by a paragraph have 32px margin-bottom? 5. Clusters — Are icon-only controls grouped at 4px with 16px to text-labeled neighbors? 6. Containers — Is card/toolbar padding ≥ internal sibling gaps? 7. Sections — Is heading→paragraph (32px) tighter than section→section? 8. Symmetry — Is section padding equal top and bottom at each breakpoint? 9. Design system — Do section, container, and component specs override anything you guessed?
---
10. Relationship to other standards
| File | Role |
|---|---|
Design system (layout.md, component modules) | Concrete tokens — section padding, container width, component padding |
| `spacing-principles.md` (this file) | How to choose and layer spacing tiers for grouping and hierarchy |
| `ui-principles.md` | Non-negotiable control rules (inputs, buttons, badges), layout pillars, proximity |
| `typography-principles.md` | Type scale, heading rhythm, line-length — pairs with §3.1 here |
When in doubt: token first, principle second, polish third.
Typography Principles
Universal typography principles covering foundations, hierarchy & scale, readability, accessibility, responsive type, and brand tone — the reasoning layer for typographic decisions.
---
0. How agents must use this document
1. Treat each pillar as a lens. Before generating or reviewing typography, scan all six pillars and identify which apply to the artifact. Most surfaces touch at least four (foundations, hierarchy, readability, accessibility). 2. Foundations first. Decide the typeface(s), the family count (1–2, never 3+), and the weights you'll use before writing any styles. Resist the urge to add another font later. 3. Build the scale once. Pick a modular ratio (1.125, 1.2, 1.25, 1.333, 1.414, 1.5, 1.618) and derive every size from the body base. Never set sizes "by eye." 4. Validate readability before aesthetics. A beautiful typeface that's unreadable at body size is a failure. Test at 16px, 14px, and zoomed-in 200%. 5. Bake accessibility in from the first line of CSS — contrast ratios, focus order on text controls, dynamic type, and prefers-reduced-motion for animated copy. 6. Design for the smallest screen first. Mobile is the majority of traffic. If type works at 360px wide it almost always works on desktop; the reverse is rarely true. 7. Justify every deviation. If you break a rule (e.g. shipping a third typeface, using sub-14px type outside badges/micro components), name the rule, explain why, and document the compensating control. 8. Font choice is yours; the rules are universal. This document never mandates a specific typeface. Where typefaces are named anywhere below, they appear only as illustrations of a category (humanist sans, transitional serif, monospace, etc.). Every rule applies to whichever typeface(s) you and your team have already chosen.
0.1 Non-negotiable layout & control rules
These rules override conflicting guidance elsewhere in this document. Apply them on every surface unless the user brief explicitly documents an exception.
Buttons never use underline
- Button labels must never be underlined — not in default, hover, focus, active, disabled, loading, or visited states.
- This applies to
<button>,<a>styled as buttons, icon buttons with visible text, and button groups. - Use weight, color, background, border, and shadow to signal interactivity — never
text-decoration: underlineon button copy. - Links styled as inline text may still underline per link rules; once an element is a button, underline is forbidden.
Big headings use tight line-height
- Display-scale headings (page hero
<h1>, major section<h2>openers, and any heading ≥ ~28px) must use `line-height: 1` (equivalent to100%). - Multi-line hero and section titles must not inherit body leading (1.4–1.6) or loose heading ranges (1.15–1.25). Loose leading creates visible gaps between wrapped lines and makes large headings look broken.
- Smaller headings (
<h3>–<h6>), card titles, and UI labels may use tighter-but-not-display leading (typically 1.1–1.25) when they wrap to multiple lines — but never apply body paragraph line-height to display headings.
Section openers: <h2> everywhere except hero
- Every major page section must open with an `<h2>` as its first heading — Features, Pricing, FAQ, Testimonials, Footer content bands, etc.
- Only the page hero uses `<h1>`. One
<h1>per page, reserved for the hero / first-impression block. - Do not open a section with
<h3>when it is a top-level page section. Sub-sections inside an<h2>block may use<h3>and below. - Card titles, FAQ questions, footer column labels, and modal titles follow the role table in §2.6 — they are not section openers even when large.
Big headings need 32px bottom margin
- Every display-scale heading (hero
<h1>and major section<h2>openers) must have `margin-bottom: 32px` before the next element (lead paragraph, supporting copy, CTA row, or section content). - Do not rely on ad-hoc spacing (16px, 24px, 48px) for these headings unless a design-system token explicitly maps to 32px for this role.
- If a heading is immediately followed by another heading (rare), only the lower heading in the pair keeps normal sub-heading spacing; the primary display heading still gets 32px before its first non-heading sibling.
Minimum font sizes
These floors apply to every marketing section, landing page band, and product surface unless the user brief explicitly documents an exception.
16px minimum — primary copy
- Body paragraphs, section headings (every
<h1>–<h6>that carries readable copy), and button labels must never ship below 16px. - This includes hero subcopy, feature titles, pricing plan names, FAQ questions, testimonial quotes, footer link columns treated as readable text, and any
<p>meant to be read — not glanced at.
14px minimum — section support text
- Secondary / supporting text inside sections — feature descriptions, pricing fine print, testimonial roles, FAQ answers, trust-band labels, social-proof captions, and similar supporting lines — must never ship below 14px.
- Applies to Features, Pricing, Testimonials, FAQ, Footer content bands, and comparable page sections. If it explains or supports a heading, treat it as support text and hold the 14px floor.
Below 14px — micro components only
- Sizes under 14px are allowed only for true micro UI: badges, pill labels, legal micro-lines inside badges, timestamp chips, and other non-reading affordances where space is genuinely constrained.
- Do not use sub-14px type for paragraphs, section headings, feature copy, pricing details, or button labels to "fit more content." Reflow, shorten copy, or restructure the layout instead.
Agent checklist before shipping type
1. Scan generated CSS and components for font-size below 14px — flag anything that is not a badge or documented micro component. 2. Scan for 12px, 13px, and 0.75rem/0.8125rem on section body copy, headings, or buttons — raise to 16px (primary) or 14px (support) as appropriate. 3. When in doubt between 14px and 16px, default to 16px.
---
Pillar 1 — Type System Foundations
1.1 The vocabulary you must use precisely
| Term | Definition |
|---|---|
| Typeface (font family) | The named design that defines the shared visual style across every character, number, and symbol. A typeface is composed of multiple fonts. |
| Font | A specific weight/style instance within a typeface — e.g. the Regular, Bold, or Bold Italic of whichever typeface you have chosen. |
| Weight | Stroke thickness expressed numerically (100–900) or by name (Thin, Regular, Medium, Bold, Black). |
| Style | Upright (Roman) vs. Italic vs. Oblique. |
| x-height | Height of the lowercase x. Typefaces with a high x-height read better at small UI sizes; typefaces with a low x-height often suit long-form display use. |
| Cap height | Height of uppercase letters from the baseline. |
| Ascender / Descender | Strokes that rise above x-height (h, b) or descend below baseline (g, p). |
| Counter | The enclosed/open negative space inside a letter (o, d). Open counters improve legibility at small sizes. |
| Tracking (letter-spacing) | Uniform spacing across a run of text. Tighten for headlines, loosen for ALL CAPS labels. |
| Kerning | Adjustment between specific letter pairs. Mostly handled by the font; intervene only for display headlines. |
| Leading (line-height) | Vertical space between baselines of consecutive lines. |
| Measure (line length) | Number of characters per line. The single biggest readability lever after font size. |
1.2 Choosing typefaces
The rules below describe categories and characteristics. Pick any typeface that fits the category — what matters is the structural traits, not the specific name.
- Default to system fonts for performance and platform familiarity unless brand requires a custom face. A platform-aware system stack ships zero bytes, scales correctly with the user's OS settings, and supports every script.
- Sans-serif for body on screen. Sans-serif faces with a high x-height and open counters outperform serifs at typical body sizes on standard-density screens. Look for humanist sans-serifs with clearly differentiated letterforms.
- Serif for editorial display. Reserve serifs for headlines, pull quotes, or brand voice that calls for tradition. On high-DPI screens the legibility gap has narrowed, so serifs can also work for long-form body if the typeface is screen-tuned.
- Monospace for code and tabular numerals. Use any monospaced family for code blocks and terminal output. For data tables in any typeface, also enable
font-variant-numeric: tabular-numsso digits align column-wise. - Avoid script and decorative typefaces for anything beyond a single logotype or hero word. They collapse below ~24px regardless of which script face you choose.
Selection process — choose a typeface in this order
1. Scope the deliverable. How is the content delivered (web app, marketing site, native app, email, print)? Who is the audience? What typefaces do competitors / category leaders use? Each delivery medium rewards different families. 2. State the tone in one sentence (e.g. "professional and approachable", "playful and energetic", "authoritative and editorial"). A typeface either reinforces that tone or undermines it. 3. Shortlist 3–5 candidates that fit the tone and the category constraints above. For most UI work, the shortlist will be high-x-height humanist sans-serifs. 4. Test each candidate with real, representative content at the actual sizes you'll ship — body, lead paragraph, heading, microcopy. Lorem ipsum hides legibility problems; copy from your actual product reveals them. 5. Test each candidate with the brand colors on both light and dark backgrounds. The same brand color sometimes loses contrast on light backgrounds while reading fine on dark ones (or vice-versa) and the typeface that survives both wins. 6. Test the ambiguous-character set: Il1 O0 rn/m a/o cl/d. The winning typeface differentiates these clearly at body size with no squinting. 7. Confirm glyph coverage for every script you'll ship (Latin Extended, CJK, Arabic, Cyrillic, etc.). Don't discover a missing glyph in production. 8. Ship the variable-font version if it exists; ship 2–4 static weights only if it doesn't.
1.3 Pairing rules
- One family is always safe. A well-designed family with Regular/Medium/Semibold/Bold weights covers 95% of needs.
- If you pair, pair across classification. One sans + one serif. Two sans-serifs that are visually similar create dissonance, not contrast.
- Match x-heights when pairing for body + headline so the visual weight matches across hierarchy levels.
- Never exceed three families on a single product (typically: brand display, UI sans, code mono).
- Test the pair on a real layout — pairing decisions should be validated on a representative page, not on isolated specimens.
1.4 Weight discipline
- Ship 2–4 weights per family. Typical ramp: Regular (400), Medium (500), Semibold (600), Bold (700). A well-designed family with these four covers ~95% of needs regardless of which family you've picked. Adding more weights bloats payload and tempts inconsistent use.
- Body copy lives at 400 or 500. Reserve 600+ for headings, emphasis, and key labels.
- Avoid Thin (100) and Light (200/300) below 18px — strokes thin out and contrast against the background drops below WCAG limits.
- Avoid Black (900) at any UI size below 24px — counters fill in and legibility drops.
- Prefer variable fonts when available: a single file delivers the full weight axis (and often width/optical-size), smaller than two static weights combined.
1.5 Loading and performance
- Subset fonts to the glyphs you actually need (Latin-1, Latin Extended, etc.). Never ship the full glyph set unless you use it.
- Use `font-display: swap` so text renders immediately in a fallback while the custom font loads. The brief Flash of Unstyled Text (FOUT) beats invisible text.
- Preload the critical face (headline + body Regular) with
<link rel="preload" as="font" crossorigin>to prevent layout shift on hero text. - Pick a fallback that matches metrics. CSS
size-adjust,ascent-override,descent-overrideon@font-facecan match the fallback to the loaded font and eliminate Cumulative Layout Shift.
1.6 Agent rules — foundations
- DO default to a system font stack unless the brief explicitly requires a custom face.
- DO keep family count to 1, push to 2 only with a justification, never reach 3+.
- DO declare 2–4 weights per family and stick to them.
- DO subset, preload, and use
font-display: swap. - DO use variable fonts when available.
- DO NOT introduce a new typeface mid-project to "spice up" a section. Reuse what exists.
- DO NOT ship Thin/Light weights below 18px or Black weights below 24px.
- DO NOT rely on Comic Sans, Papyrus, or any decorative face for system text.
1.7 Foundations checklist
- [ ] Typeface(s) chosen for the medium (screen vs. print, body vs. display).
- [ ] No more than 2 families used; 3rd only for code (mono).
- [ ] 2–4 weights per family declared.
- [ ] System-font fallback stack defined for every custom face.
- [ ] Variable font used where available.
- [ ] Glyph subsetting and
font-display: swapconfigured. - [ ] Critical fonts preloaded; fallback metrics matched.
---
Pillar 2 — Hierarchy & Scale
2.1 Why hierarchy matters
Type hierarchy lets users scan before they read. Without it every word competes equally and the eye has nowhere to land. Strong hierarchy improves UX, scannability, and SEO (search engines weight heading semantics).
2.2 The modular scale
Pick one ratio and derive every size from a single base.
| Ratio | Use case |
|---|---|
| 1.125 (Major Second) | Dense data UIs, dashboards, admin panels — subtle steps preserve density. |
| 1.2 (Minor Third) | General product UI; the safe default. |
| 1.25 (Major Third) | Marketing sites with moderate hierarchy contrast. |
| 1.333 (Perfect Fourth) | Bold marketing/landing pages. |
| 1.414 (Augmented Fourth, √2) | Print-influenced editorial. |
| 1.5 (Perfect Fifth) | High-contrast hero typography. |
| 1.618 (Golden Ratio) | Editorial / brand-led pages where the headline must dominate. |
Example with base 16px and ratio 1.25:
| Step | Size | Use |
|---|---|---|
| -2 | 10.24px → badges / micro only | Pill labels, badge chips — not section copy (§0.1) |
| -1 | 12.8px → 14px floor for support text | Section support text minimum; badges may go smaller (§0.1) |
| 0 | 16px | Body, headings, buttons — primary floor (§0.1) |
| +1 | 20px | Lead paragraph, large body |
| +2 | 25px | h4 |
| +3 | 31.25px | h3 |
| +4 | 39.06px | h2 |
| +5 | 48.83px | h1 |
2.3 Hierarchy beyond size
Size alone is brittle. Reinforce hierarchy with at least one of:
| Lever | When to use |
|---|---|
| Weight | Cheapest signal — Bold headings vs. Regular body. |
| Color | Primary heading dark; secondary copy mid-tone; tertiary muted. Reinforce, never replace, size. |
| Letter-spacing / Case | Tight tracking for large headings (-0.02em); positive tracking for ALL CAPS section labels (0.08–0.15em). |
| Family | Serif headline + sans body provides hierarchy through contrast. |
| Position | Centered hero vs. left-aligned body signals different roles. |
2.4 The four-level rule
- Limit any single surface to 3–4 hierarchy levels. Beyond that the hierarchy collapses into noise.
- One element dominates per surface. Identify the single most important word/sentence and make it visually unmistakable.
- Squint test. Reduce the design to ~25% size or blur it. The primary element should still be the first thing the eye finds.
2.5 Heading semantics
- One `<h1>` per page. Reserve it for the page hero / first-impression block only.
- Every major page section opens with `<h2>`. Features, pricing, testimonials, FAQ, and comparable bands start with
<h2>— not<h3>or styled<div>s. See §0.1. - Never skip levels (
<h2>→<h4>) — assistive tech relies on the heading outline. Restyle visually if you need a smaller-looking heading; preserve the level semantically. - `<h1>`–`<h6>` should match the visual hierarchy. A "section title" styled to look like an h2 should be an h2, not a styled
<div>.
2.6 Heading size ≠ heading level (anti-inflation rule)
A common and damaging failure mode: agents and developers conflate semantic level (<h1>–<h6>) with visual size. The result is footer column titles set as <h2> at 32px, card titles at 28px competing with the page hero, and FAQ questions rendered as display headlines. The page hierarchy collapses because every "title" looks like a section opener.
The principle: semantic level is set by the document outline; visual size is set by the role of the heading on its surface. The two are independent decisions and must be made independently.
Visual size by role
| Role | Typical level | Typical size (web) | Why this size |
|---|---|---|---|
| Page hero / first impression | <h1> | Largest in the scale (clamp ~36–72px); `line-height: 1`; `margin-bottom: 32px` | One dominant element per page. |
| Major section opener | <h2> | Second-largest (clamp ~28–44px); `line-height: 1`; `margin-bottom: 32px` | First heading inside every major section; anchors the page outline. |
| Sub-section opener | <h3> | Third-largest (~22–32px) | Used only when an <h2> section has clearly distinct sub-topics. |
| Card / tile title | <h3> or <h4> | 16–20px (often body size or one step above) | A card is one of many siblings — its title competes inside the card, not with the page. |
| Modal / dialog title | <h2> (within the dialog) | 18–24px | Anchors the dialog; never larger than the page hero behind it. |
| Accordion / FAQ question | <h3> | 16–18px | Repeats many times — must stay scannable, not shouty. |
| Footer column title | <h3> or <h4> | 16px (14px only for uppercase list-label treatment with tracking) | Functions as a list label, not a banner. |
| Sidebar / nav group label | <h3> or <h4> | 16px (14px minimum with tracking for compact nav) | A navigation cue, not a content opener. |
| List-group label inline in body | <h4> or <h5> | Body size or one step below | Quietly groups related items. |
The two questions to ask before sizing any heading
1. What is its level in the document outline? → Determines the HTML tag. 2. What is its visual role on the surface? → Determines the CSS class and font-size.
A footer "Product" column title might be <h3> semantically (it sits inside <footer> after the page's <h2> section openers) and 14px visually (it's a list label, not a section opener). Both decisions are correct and independent. A card title might be <h3> semantically and 18px visually, while an <h3> opening a sub-section in long-form content might be 28px — same tag, different role, different size.
Specific rules
- *Display-scale sizes (≥ 30px) belong only to the page hero `<h1>` and the opening heading of a major page section (`<h2>`).* Nothing else on the page should compete with them visually.
- Card / tile titles never exceed ~20px unless the card is the hero of the page (e.g. a single featured card layout). A card is a sibling of other cards; if its title is the same size as a section heading, the grid loses its identity as a group.
- Card / tile titles and footer column titles must use `<h4>` or lower, never `<h2>` or `<h3>`. Reserve
<h2>exclusively for major page-section openers. Reserve<h3>for sub-sections of an<h2>. Using<h2>or<h3>inside a repeating component (card, list, footer column) inflates the heading size because base heading styles scale<h2>and<h3>to section-opener sizes. Cards and footer columns are containers of many siblings — their titles are list labels, not section openers. Use<h4>with explicit small sizing (14–20px via utility classes). - Footer column titles, sidebar group labels, nav group labels, and any "list-label" heading must stay at or above 14px (prefer 16px for readable labels; 14px only when uppercase + letter-spacing makes the role clearly a list label, not a section opener). Always use
<h4>or<h5>, never<h2>or<h3>. - FAQ / accordion questions stay close to body size (16–18px). They appear many times in a row; size them like list items, not like headlines.
- Modal / dialog titles are larger than body but smaller than the page hero (18–24px). The modal is a temporary surface, not the main story.
- Stat / metric numerals are not headings. A "99.99%" uptime figure is a display number, not an
<h2>. Use a<p>(or<span>) with display-scale size and pair it with a small<p>label. Save heading tags for actual headings. - Define heading roles as named CSS classes (e.g.
.heading-display,.heading-section,.heading-sub,.heading-card,.heading-label) and apply them by role, not by tag. This stops the automatic mental map oftag → sizeand forces a deliberate role decision.
Quick decision tree (use this every time you write a heading)
Is this heading the FIRST IMPRESSION of the page (hero)?
├─ YES → <h1>, display-scale (36–72px clamp), line-height: 1, margin-bottom: 32px, unique on the page.
└─ NO → Is it the OPENER of a major page section?
├─ YES → <h2>, large (28–44px clamp), line-height: 1, margin-bottom: 32px, one per major section.
└─ NO → Is it a SUB-section of an <h2> (NOT inside a repeating component)?
├─ YES → <h3>, medium (~22–32px).
└─ NO → Is it INSIDE a repeating component (card, pricing tier, footer column, sidebar, FAQ)?
├─ YES → <h4> or <h5>, small (14–20px), uppercase + tracking for labels. NEVER <h2> or <h3>.
└─ NO → Probably not a heading. Use <p> with the right class.2.7 Agent rules — hierarchy
- DO pick a single modular ratio for the whole product and document it as a token (e.g.
--type-ratio: 1.25). - DO apply 3–4 levels per surface; collapse anything beyond that.
- DO establish one dominant element per view via size + weight + color combined.
- DO preserve heading semantics (
<h1>–<h6>) regardless of visual restyling. - DO open every major page section with
<h2>; reserve<h1>for the hero only (§0.1). - DO set display-scale headings (
<h1>, major section<h2>) to `line-height: 1` and `margin-bottom: 32px`. - DO match heading visual size to its role (hero / section opener / sub-section / card / list label), not to its HTML tag.
- DO restrict display-scale sizes (≥ 30px) to the page hero
<h1>and major section<h2>openers. - DO keep card/tile titles ≤ 20px and footer/sidebar/nav-group list-labels at 14px minimum (prefer 16px).
- DO define named role classes (
.heading-section,.heading-card,.heading-label, etc.) and apply by role. - DO NOT create one-off type sizes. If you need 22px and the scale offers 20 or 25, pick one and refactor — don't add an off-scale value.
- DO NOT rely on color alone for hierarchy (fails for color-vision-deficient users).
- DO NOT use multiple
<h1>s on a single page. - DO NOT open a major page section with
<h3>or lower when that section is a top-level band on the page. - DO NOT apply underline to button labels in any state (§0.1).
- DO NOT auto-size every
<h2>as a "section heading" regardless of where it appears — an<h2>inside a modal or card needs its own role-appropriate size. - DO NOT use display-scale type for repeating elements (cards, FAQ items, footer column titles, sidebar groups). If everything is a banner, nothing is.
- DO NOT wrap stat numerals or marketing slogans in
<h2>/<h3>just because they're large — those are display paragraphs, not headings.
2.8 Hierarchy checklist
- [ ] One modular ratio chosen and applied across all sizes.
- [ ] Every size traceable to the base × ratio^n.
- [ ] Maximum 3–4 visible hierarchy levels per view.
- [ ] One dominant element per surface; squint test passes.
- [ ] Heading semantics correct; no skipped levels.
- [ ] Hero uses
<h1>; every other major section opens with<h2>. - [ ] Display headings (
<h1>, major<h2>) useline-height: 1andmargin-bottom: 32px. - [ ] No underline on button labels in any state.
- [ ] Hierarchy reinforced by at least two signals (size + weight, or size + color).
- [ ] Display-scale sizes (≥ 30px) used only for the page hero
<h1>and major section<h2>openers. - [ ] Card / tile titles ≤ 20px.
- [ ] Footer column titles, sidebar/nav group labels at 14px minimum (prefer 16px).
- [ ] FAQ / accordion questions in the 16–18px range.
- [ ] Modal titles smaller than the page hero behind them.
- [ ] No
<h2>/<h3>wrapping pure stat numerals or non-heading display text.
---
Pillar 3 — Readability & Legibility
3.1 The distinction
- Legibility = can the user recognize individual characters? (typeface design, x-height, counter shape, weight)
- Readability = can the user comfortably read extended passages? (size, line-height, line length, contrast, language clarity)
3.2 The readability triad: size · line-height · measure
These three values are coupled — change one and you must reconsider the others.
Size
| Context | Recommended | Floor |
|---|---|---|
| Web body / paragraphs | 16–18px (1rem–1.125rem) | 16px (§0.1) |
| Section headings (`<h1>`–`<h6>` readable copy) | Per scale | 16px (§0.1) |
| Button labels | 16–18px | 16px (§0.1) |
| Section support text (feature descriptions, FAQ answers, pricing fine print, captions in sections) | 14–16px | 14px (§0.1) |
| Mobile native body | iOS 17pt, Android 16sp | Don't go below platform defaults |
| Form labels & UI controls | 16px | 14px never on interactive labels |
| Headlines (h1, major section h2) | 32–72px depending on hero treatment | — |
| Badges & micro components | 11–13px when space is constrained | Below 14px only here (§0.1) |
Line-height (leading)
| Context | Recommended |
|---|---|
| Body copy | 1.4–1.6 (sweet spot is 1.5–1.6 for long-form) |
| Display headings (hero `<h1>`, major section `<h2>`) | `1` / `100%` — mandatory; never use body or loose heading leading (§0.1) |
| Smaller headings (`<h3>`–`<h6>`) | 1.1–1.25 when they wrap to multiple lines |
| UI labels and buttons | 1.0–1.2; no underline on button text (§0.1) |
| Multi-line captions | 1.4 |
Larger display headings use `line-height: 1`; body and captions stay looser. This is not a "looks nice" preference — loose leading on hero lines creates broken multi-line headlines.
Measure (line length)
- Desktop body: 45–75 characters per line (CSS:
max-width: 65chis a reliable default). - Mobile body: 30–45 characters per line is acceptable; the screen forces this.
- Lines longer than 90 characters force the eye to "find" the next line and reading speed drops sharply.
- Lines shorter than ~30 characters create too many line breaks per paragraph and disrupt rhythm.
3.3 Spacing micro-rules
- Tracking on body: leave it alone. The font designer set it correctly.
- Tracking on display headlines: tighten slightly (
-0.01emto-0.03em) to compensate for visual looseness at large sizes. - Tracking on ALL CAPS labels: open it up (
0.05emto0.15em) so caps don't crash into each other. - Paragraph spacing: prefer space between paragraphs (margin-bottom ≈ 1em) over indented first lines on screen.
- Display heading spacing: hero
<h1>and major section<h2>openers use `margin-bottom: 32px` before the next sibling (§0.1). - No double spaces after a period. Ever. Modern fonts handle this.
3.4 What hurts readability
| Anti-pattern | Why it hurts | Fix |
|---|---|---|
| ALL CAPS body paragraphs | Strips ascender/descender shape cues; reduces scannability ~13%. | Reserve caps for short labels (≤2 words). |
| Justified text without hyphenation | Creates "rivers" of white space. | Use left-align for screen, or enable hyphens: auto. |
| Italic body paragraphs | Slower to read for most users. | Use italic for emphasis, titles of works, foreign terms. |
| Centered multi-line paragraphs | Each line starts at a different position, forcing a "find" action. | Center only headlines and 1–2 line callouts. |
| Pure black on pure white at full brightness | High contrast can cause eye strain. | Use near-black (#1A1A1A) on near-white (#FAFAFA) — still meets contrast. |
| Decorative scripts at small size | Counters fill in, strokes blur. | Scripts only ≥24px and only for logos/hero. |
| Underlining button text | Buttons are controls, not links; underline looks broken and conflicts with link affordance. | Forbidden in all button states (§0.1). Use weight, fill, border, shadow. |
| Underlining non-link text | Trains users that anything underlined is a link, then underlining a link doesn't help. | Use bold or color for emphasis; reserve underline for inline links only — never buttons. |
| Thin/Light weight on thin background contrast | Drops below 4.5:1. | Use Regular weight or darker color. |
3.5 Long-form vs. UI text
- Long-form (articles, docs): 18–20px body, 1.6 line-height, 65ch measure, generous paragraph spacing. Prioritize comfort over density.
- UI (forms, lists, dashboards): 14–16px body, 1.4 line-height, density-driven measure. Prioritize scanning over reading.
- Tables of numbers: enable
font-variant-numeric: tabular-numsso digits align column-wise. Right-align numeric columns; left-align text columns.
3.6 Letter case
Five case styles, each with a specific job. Pick the case by the role the text plays — never by aesthetic preference alone.
| Case | Pattern | Use for | Avoid for |
|---|---|---|---|
| Sentence case | First word capitalized, rest lowercase | Body paragraphs, button labels, form labels — almost everything. The default. | — |
| Title Case | All Major Words Capitalized (Articles, Short Prepositions, and Conjunctions Stay Lowercase) | Editorial headlines, page titles, navigation items, section headings in formal contexts. | Body text. Mid-sentence labels. |
| ALL CAPS | EVERY LETTER UPPERCASE | Short emphasis labels (≤ 2 words), category tags, eyebrow labels above headings, wordmarks. | Body text (drops scannability ~13% by removing ascender/descender shape cues). Long button labels. Conversational/chat UI (reads as yelling). |
| Small caps | Capital letterforms drawn at x-height | Distinguishing acronyms, abbreviations, or running heads from body without the visual weight of full caps. | Body text in any typeface that lacks proper small-cap glyphs — faux small caps look bad. |
| all lowercase | Every letter lowercase | Stylistic wordmarks, intentionally informal brand voice. | Anything users read for information. |
Case rules
- Default to sentence case for buttons, links, form labels, and microcopy. It is faster to read, more accessible, and feels less shouty.
- Always increase tracking on ALL CAPS by
0.05emto0.15em. Caps were never designed to sit next to each other and crash visually without spacing. - Avoid ALL CAPS in conversational interfaces (chat, comments, social, support tickets) — readers parse it as yelling.
- Use real small-cap glyphs only (
font-variant-caps: small-capsworks only when the font ships small caps). If the font doesn't ship them, skip the effect entirely; faux small caps stretch a regular cap down and look broken. - Never set an entire paragraph in ALL CAPS even for "emphasis." Use weight, color, or a higher heading level instead.
- Avoid Title Case in conversational UI; sentence case is the modern web default for buttons, dialog titles, and microcopy.
3.7 Text layout details — widows, orphans, the rag
The "boring" details that separate amateur typography from polished work. None of them require code changes most of the time — they require attention.
Widows and orphans (a.k.a. "danglies")
- Widow: a single word or very short line stranded at the end of a paragraph or headline.
- Orphan: the first line of a paragraph stranded alone at the bottom of a column or page (the rest of the paragraph wraps to the next column).
- Both disrupt visual flow and create awkward white space. Some teams prefer the gentler term "danglies" to avoid the loaded language.
Fix on the web:
- Use
text-wrap: balanceon headings and short paragraphs to let the browser distribute words evenly across lines. - Use
text-wrap: prettyon longer paragraphs (where supported) to push the last line to a healthier length. - Use a non-breaking space (
) before the last word of a headline to drag it onto the previous line if it would otherwise dangle. - Adjust the container width by a few characters — widening or narrowing often resolves a dangly without code changes.
The rag
The "rag" is the uneven right edge of left-aligned text (or the left edge of right-aligned text). A good rag is gently uneven, with no dramatic jumps in line length; a bad rag has lines that vary wildly, creating zigzag visual noise.
- Adjust container width or
letter-spacingslightly to soften a bad rag. - For narrow columns where the rag is unavoidable, accept it — forcing tight widths into clean justification creates worse rivers.
- Hyphenation (
hyphens: auto) tames the rag by allowing word breaks at line ends.
Line length under pressure
- Body line length under ~30 characters loses rhythm; over ~75 characters loses the eye between line returns.
- If you must use a wide column, increase line-height (toward 1.7–1.8) to compensate.
- If you must use a narrow column, reduce line-height slightly (toward 1.4) so the text doesn't feel lonely.
Justification — the four options
- Left-aligned (left-justified) — the default for body text on screen. Predictable starting point for every line; the rag accepts naturally varied line endings.
- Right-aligned — for tabular numerics or text paired with a strong vertical anchor on the right (e.g. timeline labels). Rarely for paragraphs.
- Centered — only for ≤ 2-line callouts, headlines, and quotes. Multi-line centered paragraphs force the eye to find each line's start.
- Fully justified — only with
hyphens: autoenabled. Without hyphenation it produces "rivers" of white space inside the paragraph.
3.8 Agent rules — readability
- DO set body type at 16–18px on web, 17pt on iOS, 16sp on Android.
- DO target a measure of 45–75 characters; enforce with
max-width: 65ch. - DO set body line-height between 1.4 and 1.6.
- DO set display headings (hero
<h1>, major section<h2>) to `line-height: 1` and `margin-bottom: 32px` (§0.1). - DO use 1.1–1.25 line-height only for smaller headings (
<h3>–<h6>) when they wrap — never body leading on display headings. - DO left-align, ragged-right text for paragraphs on screen.
- DO default to sentence case for UI labels, buttons, and microcopy.
- DO NOT underline button labels in any state (§0.1).
- DO add
0.05em–0.15emof tracking to any ALL CAPS run. - DO apply
text-wrap: balanceto headings to prevent danglies. - DO use
text-wrap: prettyon long paragraphs where supported. - DO NOT use ALL CAPS, italic, or centered text for body paragraphs.
- DO NOT use ALL CAPS in conversational interfaces (it reads as yelling).
- DO NOT justify text on screen unless
hyphens: autois also enabled. - DO NOT drop interactive text below 14px or static body text below 14px.
- DO NOT override the font's built-in kerning on body copy.
- DO NOT ship faux small caps (only enable
small-capswhen the font has real small-cap glyphs).
3.9 Readability checklist
- [ ] Body type is 16–18px on web (or platform-equivalent).
- [ ] Body line-height is 1.4–1.6.
- [ ] Measure is 45–75 characters (60–65 ideal).
- [ ] Display headings (
<h1>, major<h2>) useline-height: 1andmargin-bottom: 32px. - [ ] Smaller headings (
<h3>–<h6>) use tighter leading than body when multi-line. - [ ] No underline on button labels in any state.
- [ ] No ALL CAPS, italic, or centered body paragraphs.
- [ ] Paragraphs separated by space, not indent (on screen).
- [ ] Justified text only with hyphenation enabled.
- [ ] Tabular numerals on data tables.
- [ ] Sentence case used for UI; ALL CAPS reserved for short labels with positive tracking.
- [ ] Headings use
text-wrap: balance; long paragraphs usetext-wrap: prettywhere supported. - [ ] No widows / orphans / danglies in shipped headlines or hero copy.
- [ ] Rag is gentle, not zigzag.
---
Pillar 4 — Accessibility
Typography accessibility is non-negotiable. Per the Conflict Resolution Priority (§8), accessibility wins over every other concern.
4.1 WCAG 2.2 contrast minimums
These are the floor values every text/background combination must meet. Aim higher whenever the design allows.
| Text type | Minimum contrast |
|---|---|
| Body text (< 18pt regular, < 14pt bold) | 4.5 : 1 against background |
| Large text (≥ 18pt regular / 24px, or ≥ 14pt bold / 18.5px bold) | 3 : 1 |
| UI components & graphical objects (focus rings, icons) | 3 : 1 |
| AAA target body | 7 : 1 (aspirational; required for some regulated contexts) |
- Test contrast on the actual background, including images and gradients behind text.
- For text over images, use a scrim/overlay or text-shadow to guarantee contrast at the worst-case pixel.
- Disabled text is exempt from contrast rules but must still be perceivable as disabled.
4.2 Resizing and Dynamic Type
- Users must be able to resize text up to 200% (WCAG 1.4.4) without loss of content or functionality.
- Use `rem`/`em` units for font-size — they scale with the user's browser preference. Avoid
pxfor body text when possible. - On native mobile platforms, use the platform's dynamic/scaled text system. The platform provides named text styles that automatically adjust to the user's preferred reading size, including larger accessibility sizes. Use these styles instead of hard-coding point values.
- On native platforms, use scale-aware units for text so font sizes honor the system-level font scale preference; use density-independent units for layout.
- On the web, support `prefers-reduced-motion` for any animated copy. Provide a static end-state.
4.3 Cognitive and visual accessibility
- Accessibility-tuned typefaces (designed specifically for reading ease and letter differentiation) measurably improve comprehension for some users. Even if you don't ship one as the default, choose a typeface with high readability characteristics — high x-height, open counters, clearly differentiated letterforms — over a high-contrast geometric face whose strokes vanish at small sizes.
- Open counters and clear letter differentiation (
Ivslvs1,Ovs0,rnvsm) reduce reading errors. Test your chosen typeface by typing those exact strings at body size and checking that each character is unambiguous. - Avoid "decorative" italics for body; many dyslexic users find them harder to read than upright text.
- Provide a maximum line length cap even on wide screens — long lines are especially hard for users with low vision and cognitive disabilities.
4.4 Color & dark mode
- Test contrast in both light and dark themes. A token system that flips colors must re-verify ratios in dark mode (often a pure-black background pushes contrast too high; a near-black tone is easier on the eye).
- Don't rely on color alone to convey state (error, success, required). Pair color with an icon, text label, or weight change.
- Pure white on pure black can cause "halation" (text appears to vibrate) for some users. Prefer near-white text on a near-black background instead.
4.5 Localization & RTL
- Test with the longest expected translation. German is ~30% longer than English; Russian and Arabic vary too. Bake flex/wrap into headings.
- RTL languages (Arabic, Hebrew, Persian) flip the entire reading direction. Use logical CSS properties (
margin-inline-startovermargin-left,text-align: startovertext-align: left). - Diacritics (accents above/below letters) require slightly more line-height. Test at 1.5+ leading for accented Latin scripts and CJK.
- CJK and Arabic require different font stacks. Don't assume a Latin-only font has glyph coverage. Provide locale-specific font stacks (
font-familyper:lang(...)selector or viaunicode-rangein@font-face).
4.6 Screen-reader semantics
- Use real headings (
<h1>–<h6>), not styled<div>s. - Don't break words across inline elements (e.g.
<span>Wel</span><span>come</span>) — screen readers read this as two separate words. - Decorative typography (e.g. ASCII art, Unicode "fancy" characters like 𝓗𝓮𝓵𝓵𝓸) is announced literally by screen readers and is unreadable. Use real letters with CSS styling.
- Provide `aria-label` on icon-only buttons that contain visual letterforms (e.g. a "B" bold-toggle button).
4.7 Agent rules — accessibility
- DO verify contrast ratios in both light and dark themes before shipping any color/type combination.
- DO use
remfor font-size andemfor derived spacing so user zoom works. - DO support the platform's dynamic/scaled text system on native mobile.
- DO test the layout at 200% browser zoom and at the largest accessibility text size on mobile.
- DO use logical CSS properties for RTL support.
- DO pair color signals with text or icon signals.
- DO NOT ship pure white on pure black for body text.
- DO NOT use Unicode "stylized" letters for visual effect.
- DO NOT style
<div>s to look like headings — use real heading elements. - DO NOT ignore the longest-translation case during layout.
4.8 Accessibility checklist
- [ ] Body text contrast ≥ 4.5:1 (large text ≥ 3:1) in both themes.
- [ ] All text in
rem/em(or platform-equivalent scaling unit). - [ ] Platform dynamic/scaled text supported on native platforms.
- [ ] Layout intact at 200% zoom and largest accessibility text size.
- [ ] No critical information conveyed by color alone.
- [ ] RTL rendered correctly via logical properties.
- [ ] Heading semantics (
<h1>–<h6>) match visual hierarchy. - [ ] Longest expected translation tested in headings and buttons.
---
Pillar 5 — Responsive & Cross-Platform Typography
5.1 Why typography must be responsive
Users see your text on phones, tablets, laptops, ultra-wide monitors, watches, TVs, and AR/VR headsets. The same 48px headline that anchors a desktop hero overwhelms a 360px-wide phone. Typography that doesn't adapt fails users on the device they actually have.
5.2 Fluid typography with clamp()
The modern way: let the browser interpolate between a min and max size based on viewport width.
/* min 36px, fluid 5vw, max 72px */
h1 { font-size: clamp(2.25rem, 5vw, 4.5rem); }
h2 { font-size: clamp(1.875rem, 4vw, 2.75rem); }
h3 { font-size: clamp(1.375rem, 3vw, 1.75rem); }
p { font-size: clamp(1rem, 0.95vw + 0.85rem, 1.125rem); }- Min value = the smallest acceptable size (mobile floor — never below your accessibility floor).
- Preferred value = a viewport-relative expression that scales smoothly.
- Max value = the cap so text doesn't grow grotesquely on ultra-wide screens.
5.3 Breakpoint-based scale (when clamp isn't enough)
If you need different ratios at different breakpoints (e.g. the marketing hero uses ratio 1.618 on desktop but 1.25 on mobile to preserve mobile body density), define separate scales per breakpoint:
:root { --type-ratio: 1.25; }
@media (min-width: 1024px) { :root { --type-ratio: 1.5; } }Derive every size from the ratio token.
5.4 Platform conventions
| Platform category | Typical body size | Notes |
|---|---|---|
| Phone / tablet | 16–17pt | Use the platform's dynamic/scaled text system so the user's preferred reading size is respected. |
| Desktop | 13–16pt | Slightly smaller than phone due to greater viewing distance and higher input precision. |
| Watch / small wearable | 16pt | Compact widths; use the platform's compact typeface if one exists. |
| TV / large screen | 29pt+ | Much larger because TVs are viewed from across the room. |
| Spatial / AR-VR | Varies | Sized for the perceived distance of each window in the user's space. |
| Web | 16px body | clamp() for fluid scaling; system stack or web font. |
The key insight: text size is not just about screen pixels — it's about the user's distance from the screen. The same character must subtend roughly the same angle whether on a wearable (8 inches from the eye) or a TV (10 feet from the eye). That's why TV body type sits around 29pt and watch body type around 16pt despite the watch screen being far smaller in absolute pixels.
5.5 Container queries for component-level type
When the same component appears in multiple contexts (sidebar card, hero card, modal), use container queries so the component adapts to its width, not the viewport's:
.card { container-type: inline-size; }
.card h3 { font-size: 1.25rem; }
@container (min-width: 480px) {
.card h3 { font-size: 1.5rem; }
}5.6 Agent rules — responsive
- DO use
clamp()for fluid headings and large body type. - DO define scale ratios as CSS custom properties so they can be swapped at breakpoints.
- DO test at 320px, 360px, 768px, 1024px, 1440px, and 1920px+ viewports.
- DO respect the native platform's published interface guidelines when building native apps.
- DO use container queries when a component must adapt to its own size, not the viewport.
- DO NOT ship a single fixed font-size for everything from mobile to ultra-wide.
- DO NOT use viewport units (
vw/vh) withoutclamp()— text becomes unreadably small on phones and giant on TVs. - DO NOT assume a desktop-first scale will "shrink down" gracefully. Design mobile-first.
5.7 Responsive checklist
- [ ] Headings use
clamp()with sensible min/max. - [ ] Body type fluid or stepped at breakpoints.
- [ ] Tested at 320px, 768px, 1440px viewports.
- [ ] Native platform conventions respected (per each platform's published guidelines).
- [ ] Container queries used for component-level adaptation where appropriate.
- [ ] Largest viewport doesn't blow type up to "billboard" sizes.
---
Pillar 6 — Brand & Emotional Tone
6.1 Type carries voice
Typography is half your message. The same sentence — "We're hiring" — feels different depending on the classification of the typeface used. The categories below describe structural traits, not specific typefaces — substitute any face in the same classification and the principle holds.
- Trust — old-style and transitional serifs signal heritage; neo-grotesque sans-serifs signal precision.
- Energy — geometric display faces with wide, bold letterforms feel contemporary and commanding.
- Approachability — humanist sans-serifs with open counters and rounded terminals feel warm and human.
- Playfulness — rounded sans-serifs and soft geometric faces with circular shapes.
- Authority / editorial — high-contrast (Didone) serifs for fashion; transitional serifs for news and publishing.
- Tech / utility — any monospaced family signals code, precision, system feedback.
6.2 Brand pairing patterns
| Pattern | Pairing | Tone |
|---|---|---|
| Editorial | Display serif headline + sans body | Magazine, news, long-form blog. |
| Modern minimal | Single humanist sans, weight-driven hierarchy | Product UI, SaaS, B2B. |
| Heritage / luxury | Old-style serif everywhere | Watch brands, hotels, fashion. |
| Tech / dev | Geometric sans + monospace | Developer tools, infra. |
| Friendly consumer | Rounded sans + handwritten accent | Wellness, kids, lifestyle. |
6.3 Don't fight your audience
- B2B users want efficiency. Use a clean sans, conservative scale, dense layouts. Decorative type slows them down.
- Long-form readers want comfort. Use a serif or high-x-height sans, generous leading, ~65ch measure.
- Discovery-driven users (e-commerce, media) want hierarchy and personality. Display headlines earn attention.
6.4 Animated copy
- Use sparingly. A single moving headline anchors attention; ten compete with each other.
- Respect `prefers-reduced-motion`. Provide a static end-state.
- Don't animate body copy. It must be readable at rest.
- Type-on transitions should not exceed ~600ms for short headlines or users perceive the page as "loading."
6.5 Agent rules — brand
- DO match typeface mood to audience expectations.
- DO validate brand-driven choices against readability — beauty must coexist with usability.
- DO restrict expressive typography to display/headline roles; keep body neutral.
- DO provide static fallbacks for any animated copy.
- DO NOT chase typographic trends at the cost of legibility.
- DO NOT apply display faces to body copy "for character."
- DO NOT use brand colors that fail contrast on text — adjust the color, not the contrast rule.
6.6 Brand checklist
- [ ] Typeface choice aligns with brand voice and audience expectations.
- [ ] Display/expressive type confined to headlines, hero, callouts.
- [ ] Body remains neutral and readable.
- [ ] Animated copy has static fallback.
- [ ] Brand colors meet WCAG when used as text.
---
§8 Conflict Resolution Priority
When typography rules pull against each other, resolve in this order:
1. Accessibility — non-negotiable; wins over everything else. 2. Readability — if users can't read it, the message fails. 3. Hierarchy / scannability — users must be able to find what they need. 4. Performance — large web-font payloads hurt LCP and Time-to-Read. 5. Brand expression — important, but never at the cost of the four above. 6. Aesthetic preference — last; "I like how it looks" is not a reason to ship.
---
Related skills
How it compares
Pick typeui-fundamentals over single design-system skills when agents need cross-framework UI reasoning and accessibility guardrails, not just token values.
FAQ
When do these fundamentals apply?
They apply as a fallback whenever a design decision is not covered by the design system's tokens or component rules.
What wins when a principle conflicts with the design system?
The design system wins for concrete values; flag the conflict for review, but accessibility is non-negotiable everywhere.