Skip to Content
Puzzle

Typography & Fonts

Typography scales, named typography roles, fluid font sizes, and Google Font loading.

The theme package provides base typography scales, a set of named typography roles used by the Heading and Text components, and first-class Google Font loading for three font roles.

Font roles

Fonts are organized into three roles (FontRole): heading, body, and accent. Each role resolves to a font family variable, emitted under two equivalent names:

  • --puzzle-font-heading / --puzzle-font-family-heading
  • --puzzle-font-body / --puzzle-font-family-body
  • --puzzle-font-accent / --puzzle-font-family-accent

Without configured fonts, every role falls back to "Open Sans", sans-serif.

Loading Google Fonts

Pass a fonts map to Theme. Each entry is a FontConfig:

FieldTypeDefaultDescription
familyGoogleFontFamily—A typed Google Font family name.
weightsreadonly number[]role-based defaultsThe weights to load.
stylesreadonly ("normal" | "italic")[]["normal"]Font styles to load.
display"auto" | "block" | "swap" | "fallback" | "optional""swap"The CSS font-display strategy.
fallbackreadonly string[]role-based defaultsFallback font families.
<Theme
  fonts={{
    heading: { family: "Sora", weights: [600, 700] },
    body: { family: "Inter" },
    accent: { family: "JetBrains Mono", styles: ["normal", "italic"] },
  }}
>
  <App />
</Theme>

Theme renders the Google Fonts preconnect links and one stylesheet link per font, and builds properly quoted font-family values including your fallbacks.

Standalone font loading

GoogleFont loads a single font and sets its role variable without the rest of the theme system. Prefer Theme when you want the full token system.

import { GoogleFont } from "@puzzle-ui/theme";

<GoogleFont family="Inter" role="body" />;

createGoogleFontUrl

Builds a Google Fonts CSS2 stylesheet URL for a font configuration:

import { createGoogleFontUrl } from "@puzzle-ui/theme";

const href = createGoogleFontUrl({
  family: "Inter",
  weights: [400, 700],
  styles: ["normal", "italic"],
  display: "swap",
});

Base typography scales

These token groups form the raw scales. Font sizes use rem and letter spacing uses em.

GroupCSS variableKeys
typography.fontSize--puzzle-font-size-{key}xs (0.75rem) … 6xl (3.75rem), with base = 1rem
typography.fontWeight--puzzle-font-weight-{key}normal 400, medium 500, semibold 600, bold 700, extrabold 800
typography.lineHeight--puzzle-font-line-height-{key}none 1, tight 1.15, snug 1.3, normal 1.5, relaxed 1.625, loose 2
typography.letterSpacing--puzzle-font-letter-spacing-{key}tighter −0.05em … widest 0.1em
typography.fontGap--puzzle-font-gap-{key}headingStackGap, headingParagraphGap, paragraphGap, sectionGap

Typography roles

Named roles (TypographyStyle) describe complete text styles. Each role resolves to a font role plus size, weight, line height, and letter spacing, emitted as:

  • --puzzle-font-family-{role}
  • --puzzle-font-size-{role}
  • --puzzle-font-weight-{role}
  • --puzzle-font-line-height-{role}
  • --puzzle-font-letter-spacing-{role}
RoleFontSizeWeightNotes
display1heading40–80px fluid300Largest display text.
display2heading36–60px fluid300
headline1heading32–48px fluid400
headline2heading28–36px fluid400
headline3heading22–28px fluid400
headline4headingxl scale500
subtitle1headingbase scale500
subtitle2headingsm scale500
body1bodybase scale400Default body text.
body2bodysm scale400
buttonbodysm scale500
captionbody12px400
overlinebody10px500

The larger roles use fluid font sizes built with clamp(), scaling smoothly between a minimum and maximum size across viewport widths. The smaller roles reference the base font-size scale, so overriding typography.fontSize flows through to them.

Overriding roles

Roles can be overridden per property through tokens.typography.roles:

<Theme
  tokens={{
    typography: {
      roles: {
        headline1: { fontWeight: "600" },
        body1: { lineHeight: "1.6" },
      },
    },
  }}
>
  <App />
</Theme>

Each role override accepts font (a font role), fontSize, fontWeight, lineHeight, and letterSpacing.

;