Typography components — tokens in a convenient wrapper

Font tokens from Module 2 need to reach the product through stable components: Heading and Text (sometimes Label, Caption, Code). The goal is a shared rhythm and correct HTML semantics — not “a pretty div with font-size.”

Separation: visual style ≠ HTML level

A common mistake: <h1 className="text-sm"> for a sidebar, or <p className="text-4xl"> instead of a heading.

The right contract:

  • as / level — which tag is in the DOM (h1h6, p, span).
  • variant / size — how it looks (display, title, body, caption).
// Looks like title-md, semantically h2
<Heading as="h2" size="md">Profile settings</Heading>

// Large marketing heading, but the page already has an h1
<Heading as="h2" size="display">Skill Maps</Heading>

A page usually has one h1. Heading hierarchy should be logical (do not jump from h2 to h5 without h3).

Heading

const headingSizes = {
  display: { fontSize: '3rem', lineHeight: 1.1, fontWeight: 700 },
  lg: { fontSize: '2rem', lineHeight: 1.2, fontWeight: 700 },
  md: { fontSize: '1.5rem', lineHeight: 1.3, fontWeight: 600 },
  sm: { fontSize: '1.25rem', lineHeight: 1.4, fontWeight: 600 },
};

function Heading({ as: Comp = 'h2', size = 'md', className, children, ...props }) {
  return (
    <Comp className={cn(headingVariants({ size }), className)} {...props}>
      {children}
    </Comp>
  );
}

Text

For paragraphs, captions, secondary copy:

<Text size="md">Main paragraph</Text>
<Text size="sm" tone="muted">Secondary hint</Text>
<Text as="span" size="sm" weight="medium">Inline emphasis</Text>

Useful props: tone (default / muted / danger / success), weight, align, truncate (use carefully — truncation hurts a11y if the full text is unavailable).

Responsive type

Two workable approaches:

  1. Fluidclamp():
.text-display {
  font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem);
}
  1. Stepped — different sizes at breakpoints:
.heading-lg {
  font-size: var(--font-size-xl);
}
@media (min-width: 768px) {
  .heading-lg {
    font-size: var(--font-size-2xl);
  }
}

In the DS, lock one approach and document the scale. Do not mix “magic” px on every screen.

Semantics and accessibility

  • Headings structure the document — they are not “make it bolder.”
  • Long copy — p; short UI phrases — often span inside buttons/badges.
  • line-height and measure (45–75 characters) affect readability more than picking a “trendy” font.
  • Users may enlarge text: avoid hard height on text blocks.
  • Body text contrast ≥ 4.5:1; large text ≥ 3:1.

Connection to tokens

Components only consume tokens:

// tokens/typography.js
export const fontSize = {
  xs: '0.75rem',
  sm: '0.875rem',
  md: '1rem',
  lg: '1.125rem',
  xl: '1.25rem',
  '2xl': '1.5rem',
};

Do not hardcode 14px inside fifteen components — otherwise a brand change becomes archaeology.

Practice

  1. Implement Heading and Text with as + visual sizes.
  2. Build a sample page: h1 → section h2s → body + caption.
  3. Check the heading tree in the DevTools Accessibility panel.
  4. Add stories for all size/tone combinations.