Typography

Typography sets how Nuton text looks: Fraunces for titles, Inter for everything else, and a fixed set of type styles. Use a type style for every piece of text instead of setting sizes by hand.

  • Beta
  • Accessibility
  • Figma Typography collection + text styles
  • packages/tokens/src/figma, src/code/font.json

Families, font sizes and all type styles come from Figma. The fallback stacks, the two weights and the Fraunces axes (D6) are code-only; there are no interim typography tokens. Beta until the Figma text styles are bound to the size variables (TD-12) and the duplicate Subheading/2X Small style name is cleaned up (D9).

Overview

Two families, four groups of styles. Titles use Fraunces and carry the brand voice; labels, paragraphs and subheadings use Inter, which stays readable at small sizes in dense UI.

GroupFamilyUse it for
Title (H1–H6)FrauncesPage and section titles, lesson and space names, empty-state headlines.
LabelInter, mediumSingle-line UI text: buttons, field labels, menu items, tabs, stats.
ParagraphInter, regularRunning text that can wrap: descriptions, hints, lesson content.
SubheadingInter, mediumSmall overlines and group headings above lists, such as a course name over a lesson title.

Biology 101 · Week 3

Photosynthesis, explained

Plants turn light, water and carbon dioxide into sugar. This lesson walks through each stage with a short quiz at the end.

12 min · 3 sections

Tokens

Every value below is read from @nuton/tokens when this page builds. Primitive font tokens (families, sizes, weights) feed composite type styles; components and product code use the type styles.

Title styles

--nu-type-title-h1-*56px / 64px · 400 · -0.01emFraunces (header)

Photosynthesis, explained

--nu-type-title-h2-*48px / 56px · 400 · -0.01emFraunces (header)

Photosynthesis, explained

--nu-type-title-h3-*40px / 48px · 400 · -0.01emFraunces (header)

Photosynthesis, explained

--nu-type-title-h4-*32px / 40px · 400 · -0.005emFraunces (header)

Photosynthesis, explained

--nu-type-title-h5-*24px / 32px · 400Fraunces (header)

Photosynthesis, explained

--nu-type-title-h6-*20px / 28px · 400Fraunces (header)

Photosynthesis, explained

Label styles

--nu-type-label-x-large-*24px / 34px · 500 · -0.015emInter (body)

Start lesson

--nu-type-label-large-*18px / 30px · 500 · -0.015emInter (body)

Start lesson

--nu-type-label-medium-*16px / 28px · 500 · -0.011emInter (body)

Start lesson

--nu-type-label-small-*14px / 20px · 500 · -0.006emInter (body)

Start lesson

--nu-type-label-x-small-*12px / 20px · 500Inter (body)

Start lesson

Paragraph styles

--nu-type-paragraph-x-large-*24px / 34px · 400 · -0.015emInter (body)

Plants turn light into chemical energy they can store.

--nu-type-paragraph-large-*18px / 30px · 400 · -0.015emInter (body)

Plants turn light into chemical energy they can store.

--nu-type-paragraph-medium-*16px / 28px · 400 · -0.011emInter (body)

Plants turn light into chemical energy they can store.

--nu-type-paragraph-small-*14px / 24px · 400 · -0.006emInter (body)

Plants turn light into chemical energy they can store.

--nu-type-paragraph-x-small-*12px / 16px · 400Inter (body)

Plants turn light into chemical energy they can store.

Subheading styles

--nu-type-subheading-medium-*16px / 24px · 500Inter (body)

Biology 101 · Week 3

--nu-type-subheading-small-*14px / 20px · 500Inter (body)

Biology 101 · Week 3

--nu-type-subheading-x-small-*12px / 16px · 500Inter (body)

Biology 101 · Week 3

--nu-type-subheading-2x-small-*11px / 12px · 500Inter (body)

Biology 101 · Week 3

Each style is five tokens, --nu-type-<style>-family, -size, -line-height, -tracking and -weight, generated from the Figma text style of the same name (Title/H1 Title becomes title-h1). Figma has two styles named Subheading/2X Small; the build tells them apart by size, so the 12px one is subheading-x-small(D9 cleanup).

Anatomy of a type style

Tokens of the Label / Small type style
TokenValueSource
--nu-type-label-small-family→ font-body"Inter", var(--nu-font-fallback-body)FigmaTextStyle/Label/Small
--nu-type-label-small-size→ font-size-label-small14pxFigmaTextStyle/Label/Small
--nu-type-label-small-line-height20pxFigmaTextStyle/Label/Small
--nu-type-label-small-tracking-0.006emFigmaTextStyle/Label/Small
--nu-type-label-small-weight500FigmaTextStyle/Label/Small

Family and size are aliases of the primitive font tokens. Line height, tracking and weight are read from the text style itself.

Families and fallbacks

Font family tokens
TokenValueSource
--nu-font-header"Fraunces", var(--nu-font-fallback-header)FigmaTypography/Header
--nu-font-body"Inter", var(--nu-font-fallback-body)FigmaTypography/Body
--nu-font-fallback-headerGeorgia, "Times New Roman", serifCode-only
--nu-font-fallback-body-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serifCode-only

Figma variables hold only the family name. The fallback stacks are code-only, so text stays readable before the web font loads or if it fails.

Fraunces axes (D6)

Font variation token
TokenValueSource
--nu-font-variation-display"SOFT" 0, "WONK" 0Code-only

D6 (approved): Nuton products use Fraunces with WONK set to 0, the calm, non-wonky letterforms. The marketing site currently uses WONK 1; D6 sets 0 for the system. Apply this token wherever Fraunces is used; the title styles above already do.

Weights

Font weight tokens
TokenValueSource
--nu-font-weight-regular400Code-only
--nu-font-weight-medium500Code-only

Code-only, derived from the Figma text style names (Regular, Medium). These are the only two weights in the system.

Font sizes

Font size tokens
TokenValueSource
--nu-font-size-title-h156pxFigmaTypography/Title/H1
--nu-font-size-title-h248pxFigmaTypography/Title/H2
--nu-font-size-title-h340pxFigmaTypography/Title/H3
--nu-font-size-title-h432pxFigmaTypography/Title/H4
--nu-font-size-title-h524pxFigmaTypography/Title/H5
--nu-font-size-title-h620pxFigmaTypography/Title/H6
--nu-font-size-label-x-large24pxFigmaTypography/Label/X large
--nu-font-size-label-large18pxFigmaTypography/Label/Large
--nu-font-size-label-medium16pxFigmaTypography/Label/Medium
--nu-font-size-label-small14pxFigmaTypography/Label/Small
--nu-font-size-label-x-small12pxFigmaTypography/Label/X Small
--nu-font-size-paragraph-x-large24pxFigmaTypography/Paragraph/X large
--nu-font-size-paragraph-large18pxFigmaTypography/Paragraph/Large
--nu-font-size-paragraph-medium16pxFigmaTypography/Paragraph/Medium
--nu-font-size-paragraph-small14pxFigmaTypography/Paragraph/Small
--nu-font-size-paragraph-x-small12pxFigmaTypography/Paragraph/X Small
--nu-font-size-subheading-medium16pxFigmaTypography/Subheading/Medium
--nu-font-size-subheading-small14pxFigmaTypography/Subheading/Small
--nu-font-size-subheading-x-small12pxFigmaTypography/Subheading/X Small
--nu-font-size-subheading-2x-small11pxFigmaTypography/Subheading/2X Small

Figma size variables aren't bound to the text styles (TD-12)

The Typography/* size variables exist in Figma, but the text styles don't use them; each style has its own size. The build links them by name, so today they agree. Until the DS Owner binds the styles to the variables, a size changed in one place won't follow in the other. Output stays in px (TD-12).

Usage

1. Load the fonts and map the tokens

@nuton/tokens names the families but doesn't ship font files. Load Fraunces (with its SOFT and WONKaxes) and Inter yourself, then point --nu-font-header and --nu-font-body at them. Keep the fallback token at the end of each stack.

tsx
// app/layout.tsx
import { Fraunces, Inter } from 'next/font/google'
import '@nuton/tokens/tokens.css'

const fraunces = Fraunces({ subsets: ['latin'], axes: ['SOFT', 'WONK', 'opsz'], variable: '--font-fraunces', display: 'swap' })
const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${fraunces.variable} ${inter.variable}`}>
      {/* next/font generates hashed family names; point the Nuton font tokens at them. */}
      <body style={{
        '--nu-font-header': 'var(--font-fraunces), var(--nu-font-fallback-header)',
        '--nu-font-body': 'var(--font-inter), var(--nu-font-fallback-body)',
      } as React.CSSProperties}>
        {children}
      </body>
    </html>
  )
}

2. Apply a type style

css
.lesson-title {
  font-family: var(--nu-type-title-h4-family);
  font-size: var(--nu-type-title-h4-size);
  line-height: var(--nu-type-title-h4-line-height);
  letter-spacing: var(--nu-type-title-h4-tracking);
  font-weight: var(--nu-type-title-h4-weight);
  font-variation-settings: var(--nu-font-variation-display); /* D6, Fraunces only */
}

.lesson-summary {
  font-family: var(--nu-type-paragraph-small-family);
  font-size: var(--nu-type-paragraph-small-size);
  line-height: var(--nu-type-paragraph-small-line-height);
  letter-spacing: var(--nu-type-paragraph-small-tracking);
  font-weight: var(--nu-type-paragraph-small-weight);
  color: var(--nu-text-sub-600);
}

Tailwind names available: font-header, font-body and text-<style> for all twenty styles (text-title-h1 … text-subheading-2x-small). There are no weight, tracking or line-height utilities of their own, and no utility for the Fraunces axes, so use the arbitrary property shown.

Choosing a style

  • Use the composite --nu-type-* tokens, not the primitive --nu-font-size-* ones. Size alone loses the line height and tracking that go with it.
  • Pick the title level by importance on the page, and the HTML heading level by document structure. They don't have to match.
  • Label is for one line of UI text; Paragraph is for text that wraps. Paragraph styles have taller line heights at the same size.
  • Set colour with the text colour tokens (--nu-text-strong-950, --nu-text-sub-600), never with weight or size alone.
  • Use only the two weights. If something needs more emphasis, step up a style instead of adding a bolder weight.

In use

Nuton components set their own type styles, so you only style the text around them.

Lesson card

Biology 101 · Lesson 4

Cell structure and function

Compare plant and animal cells, then label each organelle in a short quiz.

  • 8 of 12 students finished
  • Average score 84%

Button and ChartLegend bring their own styles (Label / Small and Label / X Small). Only the overline, title and summary are styled here.

Form text

Create a space

Spaces group the lessons you teach to one class.

For example, Biology 101.

Field sets its label in Label / Small and its hint in Paragraph / X Small. Match the surrounding copy to them rather than restyling the field.

Try a style

Pick a style and type your own copy. The sample and its CSS are built from the tokens.

Photosynthesis turns light into chemical energy that the plant can store.

Paragraph / Medium: 16px / 28px · weight 400 · tracking -0.011em · Inter

css
.sample {
  font-family: var(--nu-type-paragraph-medium-family);
  font-size: var(--nu-type-paragraph-medium-size);
  line-height: var(--nu-type-paragraph-medium-line-height);
  letter-spacing: var(--nu-type-paragraph-medium-tracking);
  font-weight: var(--nu-type-paragraph-medium-weight);
}

Accessibility

Text resizing

Sizes and line heights are emitted in px (TD-12). They scale with browser zoom, which is how WCAG 1.4.4 (200%) is met, but they don't follow a changed default font size in browser settings. Test every screen at 200% zoom.

Never give a text container a fixed height. Let labels and buttons wrap, so zoomed or translated text isn't clipped.

Line length

Keep running text to about 45–75 characters per line, for example with max-inline-size: 65ch on the text block. There's no measure token yet; this is a layout rule, not a type style.

At 320px wide (WCAG 1.4.10, reflow) text must wrap without horizontal scrolling. Large titles may need a smaller style on narrow screens.

Contrast

From the tokens contrast check: --nu-text-strong-950 on white 17.93:1 and --nu-text-sub-600 on white 6.69:1 both pass. --nu-text-soft-400 is 2.49:1 and must not be used for text people need to read (C1, D7).

Line height and text spacing

WCAG 1.4.8 (AAA) suggests a line height of at least 1.5 for paragraphs. Ratios below are computed from the tokens. Layouts must also survive the 1.4.12 overrides (line height 1.5, letter spacing 0.12em, word spacing 0.16em) without clipping or overlap.

Line-height ratio of paragraph and label styles
StyleSizeLine heightRatioParagraph use
paragraph-x-large24px34px1.42Short text only
paragraph-large18px30px1.67Meets 1.5
paragraph-medium16px28px1.75Meets 1.5
paragraph-small14px24px1.71Meets 1.5
paragraph-x-small12px16px1.33Short text only
label-x-large24px34px1.42Single line only
label-large18px30px1.67Single line only
label-medium16px28px1.75Single line only
label-small14px20px1.43Single line only
label-x-small12px20px1.67Single line only

Headings and semantics

  • A type style is visual only. Use real h1–h6 elements in a logical order, then style them; screen-reader users navigate by heading level.
  • Don't make text look like a heading without the element, and don't pick a heading element just for its size.
  • Subheading styles are small. Don't use them for the only label of a control or for body text.

Guidelines and related

Use the type styles for

  • Every piece of text you write outside a Nuton component.
  • Fraunces titles for page, space and lesson names.
  • Inter labels and paragraphs for all UI and running text.

Don't

  • Set font sizes, line heights or weights by hand.
  • Use Fraunces for paragraphs, labels or controls.
  • Restyle the text inside components; choose a component variant instead.

Do and don't

Cell structure and function

Compare plant and animal cells, then label each organelle.

DoPair a Fraunces title with Inter text. The serif marks the start of a section; Inter keeps the body easy to scan.

Compare plant and animal cells, then label each organelle in a short quiz. Each cell type has its own structures to find.

Don'tDon't set running text in Fraunces. It's a display face. In long or small text it slows reading.

Your space has 3 lessons waiting for review. Open each one to approve it or send it back with comments.

DoUse Paragraph for text that wraps. Its taller line height keeps wrapped lines apart.

Your space has 3 lessons waiting for review. Open each one to approve it or send it back with comments.

Don'tDon't use Label for multi-line text. Label line heights are set for one line, so wrapped lines crowd.

Last edited 2 days ago

DoUse Sub-600 for secondary text. It reads as quieter and still passes 4.5:1.

Last edited 2 days ago

Don'tDon't use Soft-400 for text. At 2.49:1 it fails contrast for anything people need to read (C1).