Skip to content

Typography guide

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

  • 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: Typography is controlled by --af-type-* tokens for easy brand overrides.

These elements are styled automatically:

<h1>
  Title
</h1>
<h2>
  Section
</h2>
<p>
  Body text
</p>
<small>
  Caption
</small>

Use <hgroup> to visually group a heading with its subtitle or tagline. This is useful for hero sections, page headers, and card titles:

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

Important: <hgroup> provides visual grouping only. Browsers don’t implement its semantic outline algorithm behavior, so ensure your heading hierarchy (<h1><h6>) remains correct for accessibility. The element is styled with appropriate spacing to group the heading and subtitle together.

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

Use text roles when semantics and appearance need to diverge:

<h2 class="af-text-h1">
  Visually H1, semantically H2
</h2>
<p class="af-text-h3">
  Visually H3, semantically paragraph
</p>
<p class="af-text-body">
  Explicit body role
</p>
<p class="af-text-caption">
  Caption text
</p>
<span class="af-text-overline">
  Overline label
</span>
<div class="af-text-display">
  Hero headline
</div>

Available roles:

  • af-text-display
  • af-text-h1af-text-h6
  • af-text-body
  • af-text-caption
  • af-text-overline

For marketing pages and dashboards:

  • af-eyebrow – Section label (uppercase, letter-spacing). Add af-is-on-primary for hero/CTA on primary background.
  • af-text-fluid-h1, af-text-fluid-h2, af-text-fluid-h3 – Responsive hero headings (clamp-based).
  • af-stat-display – Large hero numbers (e.g. “12.4k”, “0”, “1”).
  • af-prose / af-measure – Readable line length (65ch). Variants: af-measure-narrow (45ch), af-measure-wide (80ch).
<p class="af-eyebrow">
  Why Airframe
</p>
<h1 class="af-text-fluid-h1">
  Hero heading
</h1>
<p class="af-stat-display">
  12.4k
</p>
<div class="af-prose">
  Long-form content with optimal line length
</div>

These are intentionally small and common:

  • af-text-muted – muted text color
  • af-text-on-primary – text on primary background (hero/CTA)
  • af-text-on-primary-muted – muted text on primary (opacity 0.7)
  • af-text-mono – monospace font
  • af-text-sm / af-text-md / af-text-lg – simple size helpers
  • af-title – quick title style

Text alignment utilities that respect document direction (LTR/RTL):

<!-- Base alignment -->
<p class="af-text-center">
  Centered text
</p>
<p class="af-text-start">
  Start-aligned (left in LTR, right in RTL)
</p>
<p class="af-text-end">
  End-aligned (right in LTR, left in RTL)
</p>
<!-- Responsive pattern: center on small, start on md+ -->
<h2 class="af-text-h2 af-text-center af-text-start@md">
  Responsive, RTL-safe heading
</h2>

Available classes:

  • af-text-center – Center alignment
  • af-text-start – Start alignment (RTL-safe)
  • af-text-end – End alignment (RTL-safe)

Responsive variants (v0.1):

  • af-text-center@{bp} – Center at breakpoint and up
  • af-text-start@{bp} – Start at breakpoint and up

Why start/end instead of left/right?

  • start = left in LTR, right in RTL
  • end = right in LTR, left in RTL
  • Automatically respects [dir="ltr"] and [dir="rtl"]
  • No JavaScript needed
  • Standards-compliant and future-proof

Recommended Pattern:

<!-- Center on small screens, start-aligned from md up (RTL-safe) -->
<h2 class="af-text-h2 af-text-center af-text-start@md">
  Responsive, RTL-safe heading
</h2>

Behavior:

  • xs / sm: Centered
  • md+ (LTR): Left-aligned
  • md+ (RTL): Right-aligned

Typography uses semantic tokens so brands can override safely:

:root {
  /* Font primitives */
  --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;

  /_ Semantic font tokens _/
  --af-font-primary: var(--af-base-font-sans); /_ Primary font (used for body text by default) _/
  --af-font-heading: var(--af-font-primary); /_ Heading font (applied to h1-h6, .af-text-h_, .af-text-display) _/
  --af-font-body: var(--af-base-font-sans); /_ Body text font _/
  --af-font-code: var(--af-base-font-mono); /_ Code font _/
  --af-font-mono: var(--af-base-font-mono); /_ Monospace font */
}

Customizing fonts:

:root {
  /* Use custom web fonts */
  --af-font-primary: 'Inter', 'Roboto', sans-serif;
  --af-font-heading: 'Playfair Display', serif;  /* Different font for headings */
  --af-font-body: var(--af-font-primary);         /* Body uses primary */
}
:root {
  /* Sizes */
  --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;

  /_ Leading (line-height) - typography term for vertical spacing between lines _/
  /_ Use --af-leading-_ tokens; these map to CSS line-height property */
  --af-leading-tight: 1.1;
  --af-leading-normal: 1.5;
  --af-leading-loose: 1.7;

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

  /_ Roles _/
  --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;
  --af-type-h2-tracking: -0.01em;
  --af-type-h3-tracking: 0em;
  --af-type-h4-tracking: 0em;
  --af-type-h5-tracking: 0em;
  --af-type-h6-tracking: 0em;
}
<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.

For class-level utilities (af-text-*, alignment, wrap), see Typography utilities.