Skip to content

Typography

Airframe styles semantic elements by default and provides a small set of text roles for when semantics and visuals diverge.

This page covers the philosophy, tokens, and every class from typography.css. For utility tweaks (muted, wrap, numeric, alignment helpers), see Typography utilities.

  • Semantic first: <h1>–<h6>, <p>, <small> look good by default. Use semantic elements by default.
  • Text roles only for visual hierarchy overrides: Use af-text-h* or af-text-body only when semantics and visuals must diverge.
    • ✅ <h2 class="af-text-h1"> — visually H1, semantically H2
    • ✅ <p class="af-text-caption"> — caption styling on paragraph
    • ❌ <h1 class="af-text-h1"> — redundant, use <h1> instead
  • Token-driven: Override --af-font-* and --af-type-* in your stylesheet — same CSS-variable model as Theming. No rebuild.

Fonts and scale are CSS variables. Start here:

:root {
  --af-font-primary: 'Inter', system-ui, sans-serif;
  --af-font-heading: 'Inter', system-ui, sans-serif;
  --af-font-body: var(--af-font-primary);
  --af-text-lg: 1.125rem;
}

Light, dark, and high-contrast themes do not require separate font setups unless you want them. Full token lists: Tokens full list.

These elements are styled automatically — no classes required:

Title

Section

Subsection

Minor heading

Label heading

Fine heading

Body text

Caption
<h1>Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<h4>Minor heading</h4>
<h5>Label heading</h5>
<h6>Fine heading</h6>
<p>Body text</p>
<small>Caption</small>

Use <hgroup> to visually group a heading with its subtitle or tagline:

Page Title

Subtitle or tagline text

<hgroup>
  <h1>Page Title</h1>
  <p class="af-lead">Subtitle or tagline text</p>
</hgroup>

<hgroup> provides visual grouping only. Keep your heading hierarchy (<h1>–<h6>) correct for accessibility.

Headings, paragraphs, lists, blockquotes, and dividers include sensible spacing by default. You should rarely need manual spacing for typical long-form content.

Use when semantics and appearance need to diverge. Every role:

Display

Text H1

Text H2

Text H3

Text H4

Text H5

Text H6

Body role

Body small

Caption

Text sm (same size as caption)

Overline
<div class="af-text-display">Display</div>
<p class="af-text-h1">Text H1</p>
<p class="af-text-h2">Text H2</p>
<p class="af-text-h3">Text H3</p>
<p class="af-text-h4">Text H4</p>
<p class="af-text-h5">Text H5</p>
<p class="af-text-h6">Text H6</p>
<p class="af-text-body">Body role</p>
<p class="af-text-body-sm">Body small</p>
<p class="af-text-caption">Caption</p>
<p class="af-text-sm">Text sm (same size as caption)</p>
<span class="af-text-overline">Overline</span>
ClassRole
af-text-displayHero / marketing display
af-text-h1 … h6Visual heading step without changing outline
af-text-bodyExplicit body role
af-text-body-smBody weight and leading, smaller size
af-text-caption / af-text-smCaption / fine print
af-text-overlineUppercase label with letter-spacing

Lead — larger intro, muted.

Eyebrow label

Eyebrow on primary

<p class="af-lead">Lead — larger intro, muted.</p>
<p class="af-eyebrow">Eyebrow label</p>
<p class="af-eyebrow af-is-on-primary" style="background: var(--af-color-primary); padding: 0.5rem 0.75rem; border-radius: var(--af-radius);">Eyebrow on primary</p>
  • af-lead — larger intro copy, muted color
  • af-eyebrow — uppercase section label
  • af-eyebrow.af-is-on-primary — readable on a primary surface

Fluid H1

Fluid H2

Fluid H3

12.4k

<p class="af-text-fluid-h1">Fluid H1</p>
<p class="af-text-fluid-h2">Fluid H2</p>
<p class="af-text-fluid-h3">Fluid H3</p>
<p class="af-stat-display">12.4k</p>
  • af-text-fluid-h1, af-text-fluid-h2, af-text-fluid-h3 — clamp-based hero headings
  • af-stat-display — large hero numbers

Still pair marketing type with a real heading in the outline when SEO and assistive tech need one.

Readable line length with line-height: 1lh:

af-prose — 65ch. Comfortable for articles and long paragraphs.

af-prose-narrow — 45ch. Short asides and callouts.

af-prose-wide — 80ch. Leads and wider marketing copy.

<p class="af-prose">af-prose — 65ch. Comfortable for articles and long paragraphs.</p>
<p class="af-prose-narrow">af-prose-narrow — 45ch. Short asides and callouts.</p>
<p class="af-prose-wide">af-prose-wide — 80ch. Leads and wider marketing copy.</p>
ClassMax width
af-prose65ch
af-prose-narrow45ch
af-prose-wide80ch

Use one class — prefer af-prose-wide alone, not af-prose af-prose-wide.

Native <blockquote> and af-blockquote:

Quoted statement with default blockquote styling.

Same look with an explicit class when you need it.

<blockquote>
  <p>Quoted statement with default blockquote styling.</p>
</blockquote>
<blockquote class="af-blockquote">
  <p>Same look with an explicit class when you need it.</p>
</blockquote>

<code> outside a <pre> is styled automatically:

Use af-prose for readable line length.

<p>
  Use
  <code>af-prose</code>
  for readable line length.
</p>

Code blocks use the Code Block pattern.

These live in utilities, but show up often with type roles:

Muted

Strong

Monospace

Title helper

Text md

Text lg

On primary

On primary muted

<p class="af-text-muted">Muted</p>
<p class="af-text-strong">Strong</p>
<p class="af-text-mono">Monospace</p>
<p class="af-title">Title helper</p>
<p class="af-text-md">Text md</p>
<p class="af-text-lg">Text lg</p>
<p class="af-text-on-primary" style="background: var(--af-color-primary); padding: 0.5rem 0.75rem; border-radius: var(--af-radius);">On primary</p>
<p class="af-text-on-primary-muted" style="background: var(--af-color-primary); padding: 0.5rem 0.75rem; border-radius: var(--af-radius);">On primary muted</p>

Full utility lists: Text style, Text wrap, Text align, Numeric.

Dir-aware utilities (start / end follow LTR and RTL):

Centered

Start-aligned

End-aligned

Center, then start from md

<p class="af-text-center">Centered</p>
<p class="af-text-start">Start-aligned</p>
<p class="af-text-end">End-aligned</p>
<p class="af-text-h4 af-text-center af-text-start@md">Center, then start from md</p>

Responsive variants: af-text-center@{bp}, af-text-start@{bp}, af-text-end@{bp}. Details: Text alignment.

:root {
  --af-base-font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
  --af-base-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;

  --af-font-primary: var(--af-base-font-sans);
  --af-font-heading: var(--af-font-primary);
  --af-font-body: var(--af-base-font-sans);
  --af-font-code: var(--af-base-font-mono);
  --af-font-mono: var(--af-base-font-mono);
}
:root {
  --af-text-xs: 0.75rem;
  --af-text-sm: 0.875rem;
  --af-text-md: 1rem;
  --af-text-lg: 1.125rem;
  --af-text-xl: 1.25rem;
  --af-text-2xl: 1.5rem;
  --af-text-3xl: 1.875rem;

  --af-leading-tight: 1.1;
  --af-leading-normal: 1.5;
  --af-leading-loose: 1.7;

  --af-weight-regular: 400;
  --af-weight-medium: 500;
  --af-weight-semibold: 600;
  --af-weight-bold: 700;

  --af-type-h1-size: var(--af-text-3xl);
  --af-type-h1-weight: var(--af-weight-semibold);
  --af-type-h1-leading: var(--af-leading-tight);
  --af-type-h1-tracking: -0.02em;
}

Role tokens (--af-type-h*, tracking, body/caption) are listed in full on Tokens full list.

Card Title

Body text remains semantic and readable.

<div class="af-card">
  <h3 class="af-text-h2">Card Title</h3>
  <p class="af-card__body">Body text remains semantic and readable.</p>
</div>
  • Prefer semantic elements (<h1>–<h6>, <p>, <small>) by default.
  • Use text roles only when you need visual hierarchy overrides.
  • Override typography via tokens, not by increasing selector specificity.

Browse typography on the patterns gallery. Utilities: Typography utilities.