Typography

The single reference for type in Wildflower — families, weights, the size scale, heading styles, and spacing. If you are picking a font size, this page is the place to look it up.

Source of truth: packages/web-tailwind/partials/typography.css.

Reach for a utility, not a raw size. heading-*, text-body, and text-mono bundle family, size, weight, and line height into one class and stay responsive. Use raw text-* sizes only when no utility fits.


Font families

Three families, all loaded from a single Typekit kit in src/app/layout.tsx. Each font-* utility maps to a --font-* custom property declared in @theme.

UtilityFamilyWeights availableUse
font-displaypolymath400, 600Headings and display type
font-sanspolymath-text400, 500, 700 (+ italics)Body copy, UI labels — the default
font-monoibm-plex-mono400, 800Code, token names, eyebrows

Every family falls back through -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'segoe ui', 'Ubuntu', 'roboto', 'noto', Arial (or ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas for mono).

font-displaypolymath

Headings and display type. Paired with every heading-* utility.

font-normal · 400How data gets done
font-semibold · 600How data gets done
font-sanspolymath-text

Body copy, UI labels, and anything not a heading. The default.

font-normal · 400How data gets doneHow data gets done
font-medium · 500How data gets doneHow data gets done
font-bold · 700How data gets doneHow data gets done
font-monoibm-plex-mono

Code, token names, and the tag utility.

font-normal · 400How data gets done
font-extrabold · 800How data gets done

Only the weights above actually exist. Asking for a weight the kit does not ship gets you a browser-synthesized approximation, which looks subtly wrong. Two mismatches to know about: font-display has no 500 or 700, so font-medium and font-bold on a heading are not real weights — use font-semibold (600) or font-normal (400). And font-mono has no 500, 600, or 700, so the common eyebrow recipe font-mono ... font-semibold renders at 400. Use font-normal or font-extrabold (800) with mono.

dbt Summit faces

Three extra display faces exist for the dbt Summit microsite and Summit campaign blocks. Do not use them in general marketing pages.

UtilityFamily
font-summit-areaarea-normal
font-summit-interinter-variable
font-summit-bugrinobugrino

bugrino is self-hosted via an @font-face rule in typography.css (weight 500) so it works outside the Summit layout, which loads it through next/font.


Type scale

Twelve steps, declared as --text-* properties in @theme. Sizes are in rem and assume a 16px root.

UtilityrempxDefault line height
text-xs0.75rem12px1.333 → 16px
text-sm0.875rem14px1.429 → 20px
text-base1rem16px1.5 → 24px
text-lg1.125rem18px1.556 → 28px
text-xl1.25rem20px1.4 → 28px
text-2xl1.5rem24px1.333 → 32px
text-3xl2rem32px1.2 → 38.4px
text-4xl2.25rem36px1.111 → 40px
text-5xl3rem48px1 → 48px
text-6xl4rem64px1 → 64px
text-7xl4.5rem72px1 → 72px
text-8xl5.5rem88px1 → 88px

Three steps are deliberately retuned from Tailwind's stock scale — text-3xl (30px → 32px), text-6xl (60px → 64px), and text-8xl (96px → 88px). If a design comp says 32px, that is text-3xl here, not text-4xl.

text-xs0.75rem · 12pxAa
text-sm0.875rem · 14pxAa
text-base1rem · 16pxAa
text-lg1.125rem · 18pxAa
text-xl1.25rem · 20pxAa
text-2xl1.5rem · 24pxAa
text-3xl2rem · 32pxAaoverrides Tailwind default 1.875rem / 30px
text-4xl2.25rem · 36pxAa
text-5xl3rem · 48pxAa
text-6xl4rem · 64pxAaoverrides Tailwind default 3.75rem / 60px
text-7xl4.5rem · 72pxAa
text-8xl5.5rem · 88pxAaoverrides Tailwind default 6rem / 96px

Raw text-* classes set a line height you did not choose. Tailwind pairs each size with a default ratio, and those ratios are inherited from Tailwind's original scale rather than tuned for ours. Anything typographically important should set leading-* explicitly or use a heading-* / text-body utility.


Headings

Six heading utilities. Each one applies font-display, a size, font-semibold, and a line height — and all but heading-5 step up a size at the xl breakpoint (1280px).

UtilityBelow xlAt xl and upLine heightUse
heading-displaytext-6xl / 64pxtext-8xl / 88px1Hero statements
heading-1text-5xl / 48pxtext-6xl / 64px1.2Page title
heading-2text-3xl / 32pxtext-5xl / 48px1.2Major sections
heading-3text-2xl / 24pxtext-3xl / 32px1.2Subsections, card titles
heading-4text-xl / 20pxtext-2xl / 24px1.2Nested subsections
heading-5text-lg / 18px—1.2Eyebrows, labels

The live specimens below render at your current viewport width, so widen or narrow the window to see the xl step.

heading-displaytext-6xl (64px) → xl:text-8xl (88px) · leading 1 · font-semibold

How data gets done

Hero statements. One per page, at most.

heading-1text-5xl (48px) → xl:text-6xl (64px) · leading 1.2 · font-semibold

How data gets done

Page title. One per page.

heading-2text-3xl (32px) → xl:text-5xl (48px) · leading 1.2 · font-semibold

How data gets done

Major section headings.

heading-3text-2xl (24px) → xl:text-3xl (32px) · leading 1.2 · font-semibold

How data gets done

Subsections and card titles.

heading-4text-xl (20px) → xl:text-2xl (24px) · leading 1.2 · font-semibold

How data gets done

Nested subsections, smaller card titles.

heading-5text-lg (18px) · no xl step · leading 1.2 · font-semibold

How data gets done

Eyebrows, labels, and the smallest heading rung.

Heading utilities are not heading tags

Appearance and document structure are separate decisions. Pick the tag from the page outline and the appearance from the design.

Use the base components in packages/web-ui/components/Elements.tsx. Nine times out of ten these are all you need — each one pairs a heading-* utility with its matching semantic tag, and the as prop overrides the tag when the outline calls for something different.

ComponentDefault tagApplies
SuperH1<h1>heading-display
H1<h1>heading-1
H2<h2>heading-2
H3<h3>heading-3
H4<h4>heading-4
H5<h5>heading-5
H6<h6>text-xs md:text-sm font-bold (not a heading-* utility)
import { H2, SuperH1 } from '@dbt-labs/web-ui'
 
{/* Standard case — appearance and tag already agree */}
<H2>How data gets done</H2>
 
{/* A page title that should look smaller than heading-1:
    keep the h1 tag, borrow the heading-2 appearance */}
<H2 as='h1'>Pricing</H2>
 
{/* A visually large statement that is not the page title */}
<SuperH1 as='h2'>How data gets done</SuperH1>

The same file also exports Tag (a span with the tag utility), UL, and OL.

Props: as sets the rendered tag, className is appended to the component's default classes, and customID overrides the id. Every component derives an id from its own text content by default, so headings are deep-linkable for free.

Because className is appended rather than merged, a utility that collides with one of the component's defaults will not reliably win on specificity alone — this is why you will see ! flags in existing code, e.g. <H5 as='h2' className='!mb-2'>. Use ! when you need to override a default.

Reach for a raw heading-* class only in the rare case where you are adjusting text outside these components and the as and className props cannot express it. Prefer the components by default.

Keep one h1 per page and do not skip levels in the tag hierarchy.


Body and mono text

UtilityApplies
text-bodyfont-sans + text-base (16px) + leading-[1.5]
text-monofont-mono + text-base (16px) + leading-[1.5]

text-body is the default for paragraph copy. For secondary or dense UI text, drop to text-sm with an explicit leading-[1.5].

text-body and text-mono are currently declared twice — once in partials/typography.css and again in partials/tokens.css. Both definitions are identical, so there is no behavioral difference today, but edit both or neither until the duplication is removed.

Pair text color with a semantic token

Size and color are separate decisions. Use the semantic text tokens rather than raw palette values.

<p className='text-body text-neutral-secondary'>
  Body copy in the standard secondary text color.
</p>

See Design tokens for the full list.


Line height

There is no leading-* theme override, so Tailwind's stock leading utilities apply. The type system uses only two ratios in practice:

ContextLine height
Headings (heading-1 through heading-5)leading-[1.2]
heading-displayleading-[1]
Body and mono (text-body, text-mono)leading-[1.5]
Uppercase eyebrowsleading-[1.1]

Rules of thumb: as type gets larger, leading gets tighter. Body copy needs 1.5 for readability at long measures; display type at 64px and up needs 1 or the lines drift apart.


Letter spacing

Wildflower does not override Tailwind's tracking-* scale, and normal text — body copy and headings alike — should use default tracking. Never letter-space lowercase body copy.

The one established exception is uppercase eyebrow labels, where tracking is required to keep all-caps legible. The de-facto recipe across the site is:

<p className='font-mono text-xs font-normal uppercase tracking-[0.2em] text-brand-primary'>
  Case study
</p>

Tracking values in use for eyebrows range from tracking-[0.1em] to tracking-[0.2em] (tracking-widest is 0.1em). Prefer tracking-[0.2em] for small mono eyebrows and tracking-[0.1em] at text-sm and above.

This eyebrow pattern is a convention, not a utility — there is no eyebrow class in the theme. Copy the recipe above rather than inventing new tracking values, and note the font-normal: mono has no semibold.


Text spacing and measure

The theme provides no vertical-rhythm utility, so spacing between text elements is set per layout with standard Tailwind spacing. Use these defaults for consistency.

Vertical spacing

RelationshipSpacing
Eyebrow → headingmb-3 (12px)
Heading → body copymb-4 (16px)
Between paragraphsspace-y-4 (16px)
Between grouped text blocksspace-y-6 (24px)
Between subsectionsspace-y-8 (32px)
Between page sectionshandled by Section — py-12 md:py-16

Prefer a space-y-* on the container over margins on individual children — it keeps the first and last child flush with the container edges.

<div className='space-y-6'>
  <div>
    <p className='mb-3 font-mono text-xs uppercase tracking-[0.2em] text-brand-primary'>
      Platform
    </p>
    <h2 className='heading-2 mb-4'>How data gets done</h2>
    <p className='text-body text-neutral-secondary max-w-2xl'>
      Body copy, constrained to a readable measure.
    </p>
  </div>
</div>

Measure (line length)

Long lines are hard to read. Constrain body copy to a maximum width rather than letting it span a full 12-column grid.

ConstraintWidthUse
max-w-2xl42rem / 672pxStanding default for body copy
max-w-3xl48rem / 768pxWider intro or lede paragraphs
max-w-4xl56rem / 896pxHeadings that need to break across lines

Aim for 60–80 characters per line. Headings can run wider than body copy because they are short.


Choosing a size

  1. Is it a heading? Use a base component from Elements.tsx (H1–H6, SuperH1), chosen by hierarchy, not by pixel value — with as if the tag needs to differ.
  2. Is it paragraph copy? Use text-body, constrained with max-w-2xl.
  3. Is it code, a token, or an eyebrow? Use font-mono with an explicit size.
  4. Is it dense or secondary UI text? Use text-sm with leading-[1.5] and a text-neutral-tertiary token.
  5. Only if none of the above fit, use a raw text-* size — and set leading-* explicitly.

Source files

FilePurpose
packages/web-tailwind/partials/typography.cssFont families, --text-* scale, heading-*, text-body, text-mono, tag
packages/web-tailwind/partials/tokens.cssSemantic text color tokens (plus duplicate text-body / text-mono)
packages/web-tailwind/partials/components.csslink-styles, focus-styles, form input type styles
packages/web-tailwind/theme.cssCSS entry point that imports all partials
packages/web-ui/components/Elements.tsxH1–H6, SuperH1, Tag, UL, OL — the preferred way to render headings
apps/wildflower/src/app/layout.tsxTypekit stylesheet that loads the families