Skip to content

Typography

Font families, the type scale and six independent text roles: heading, body, prose, UI, metadata and code.

Each role owns its size, weight, line height and letter spacing, so a role can change without affecting the others. Line heights are unitless ratios and letter spacing is relative to the element's font size. Families and the primitive scale are shared by every role.

Headings use the heading role and ordinary reading text inherits the body role without a class. Use prose for continuous authored writing, ui for interface labels and metadata for content references. formatted composes paragraphs, lists and other editorial HTML in whichever role it inherits, and typescale scales every role locally.

01Font families

Serif supplies editorial headings and authored prose, sans-serif supports reading, and monospace serves interface text, metadata and code. A family alone does not define a typography role.

TokenRoleDefinition
--font-serifEditorial headings, large page titles and authored prose."Teodor", serif
--font-sansParagraphs and reading text."Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif
--font-monoInterface, navigation, metadata and code."Roboto Mono", monospace

Monospace alone preserves mixed case; the .ui and .metadata roles apply their own case and tracking.

base.css declares every font face; each font is self-hosted through @dg/ui/fonts/* and no font service is contacted. Headings use Teodor, a variable font loaded from fonts/Teodor-Variable.woff2, then the system serif; its italics use the font's slant axis, capped at 10°. Reading text uses Roboto and interface text Roboto Mono, both variable Latin subsets; characters outside the subset fall back to Helvetica Neue and the system monospace.

<code style="font-family: var(--font-mono)">galleryName = "Distributed Gallery"</code>

02Type scale

All type sizes are calculated from --font-size-base, whose default is set slightly below the browser root size and follows it. Change this one length to scale every role; spacing does not follow it. Body text uses the base itself; every other role and heading level derives its own factor from it. A shared step is added only when several roles share a size.

TokenRoleDefinition
--font-size-baseReference length for the entire typography scale; spacing does not follow it.0.95rem

To scale every role inside one element, add .typescale where its local --font-size-base is set; inherited role sizes do not otherwise follow a descendant's base. Use rem or px, or register --font-size-base as a <length> with @property for a container-relative base. Spacing is unchanged.

<section class="typescale" style="--font-size-base: 1.5rem">
  <h1>A larger presentation</h1>
  <p>All type roles follow the same base.</p>
  <p class="metadata">A proportionally sized reference</p>
</section>

03Heading role

Native h1–h6 headings and Heading use the serif family, set light and tight. Heading follows its semantic level unless its size is explicitly overridden.

TokenRoleDefinition
--heading-1-font-sizeDefault h1 size; fluid with the viewport between root-relative bounds.calc(var(--font-size-base) * 4.5)
--heading-2-font-sizeDefault h2 size in the shared heading scale.calc(var(--font-size-base) * 2)
--heading-3-font-sizeDefault h3 size in the shared heading scale.calc(var(--font-size-base) * 1.75)
--heading-4-font-sizeDefault h4 size in the shared heading scale.calc(var(--font-size-base) * 1.5)
--heading-5-font-sizeDefault h5 size in the shared heading scale.calc(var(--font-size-base) * 1.25)
--heading-6-font-sizeDefault h6 size in the shared heading scale.calc(var(--font-size-base) * 1.125)
--heading-font-weightDefault HTML headings.100
--heading-line-heightDefault HTML headings.1.25
--heading-letter-spacingEditorial headings, set tight.-0.07em

H1 grows with the viewport between bounds proportional to the base where typed division is supported, with a proportional fallback elsewhere; H2–H6 are fixed multiples of the base. Choose heading levels for document hierarchy; an appearance override does not change that hierarchy.

04Body role

The document default, inherited by every element that sets no other role: paragraphs, descriptions and reading text in the sans-serif family. It needs no class.

TokenRoleDefinition
--body-font-sizeReading text and descriptions.var(--font-size-base)
--body-font-weightParagraphs and reading text.200
--body-line-heightDocument default, inherited by reading text.1.5
--body-letter-spacingReading text and descriptions.0em

For a local body-size override, set --body-font-size and also resolve font-size: var(--body-font-size) on that scope so descendants inherit it.

05Prose role

The .prose class sets continuous authored writing, such as a presentation or a text about an artwork, in the serif family. Its size is slightly above the body size to offset the serif's smaller x-height. Interface descriptions and short reading text keep the body role.

TokenRoleDefinition
--prose-font-sizeContinuous authored writing in the serif family.calc(var(--font-size-base) * 1.125)
--prose-font-weightContinuous authored writing.100
--prose-line-heightContinuous authored writing, independent of the body rhythm.1.6
--prose-letter-spacingContinuous authored writing.0em

Combine .prose with .formatted for structured HTML and with .constrained-width for the reading measure.

<div class="formatted prose constrained-width">
  <p>A text written by the artist about the work.</p>
</div>

06UI role

The .ui class sets navigation, actions and interface labels in uppercase monospace. Components apply it to their controls; native label, legend and select take the same role without the class, as described in native forms.

TokenRoleDefinition
--ui-font-sizeNavigation, controls and interface labels.calc(var(--font-size-base) * 0.75)
--ui-font-weightNavigation, controls and labels.250
--ui-line-heightInterface text, independent of the body rhythm.1.6
--ui-letter-spacingNavigation, controls and interface labels.0.1em

Uppercase is applied through CSS without changing data. The role defines its own normal style, size, weight, rhythm and tracking and inherits its color.

<nav class="ui">Collection</nav>

07Metadata role

The .metadata class sets dates, authors, sources and references in uppercase monospace with muted color. It is slightly smaller than interface text, with wider letter spacing. It does not require a UI parent.

TokenRoleDefinition
--metadata-font-sizeDates, authors, sources and references accompanying content.calc(var(--font-size-base) * 0.6875)
--metadata-font-weightDates, authors, sources and references.250
--metadata-line-heightContent references, independent of the interface rhythm.1.6
--metadata-letter-spacingDates, authors, sources and references.0.16em

Form help, validation errors and status feedback also use this role.

<p class="metadata">Collection · September 2026</p>

08Code role

The .code class sets code blocks and literal data, such as token names and values, in monospace at a reduced size. Unlike the UI and metadata roles, it keeps the source casing and slightly tightens its tracking.

TokenRoleDefinition
--code-font-sizeCode blocks and literal data, such as token names and values.calc(var(--font-size-base) * 0.85)
--code-font-weightCode blocks and literal data.200
--code-line-heightCode blocks and data displays, independent of the body rhythm.1.6
--code-letter-spacingSlightly tightens Roboto Mono's fixed advance.-0.05em

Every pre, code, kbd and samp element takes this role's family, weight and tracking, upright and in source casing, even inside .ui or .metadata. pre blocks also take its size and line height; .formatted adds a surface around them. Inline code, kbd and samp keep the size and line rhythm of their sentence.

<span class="code">--background: oklch(100% 0 0)</span>

09Strong emphasis

Strong emphasis raises the weight of text inside any role and keeps its family, size, casing and color. It marks importance and the keys of structured content, not interactive states: a heavier current or hovered item would change width and move its neighbors.

TokenRoleDefinition
--strong-font-weightStrong importance, table headers and definition terms.400

Every strong, b, th and dt element takes this weight, inside or outside .formatted. A role class on the element itself sets that role's weight instead, so give the role to an ancestor: the header cells of a table keep this weight inside thead, which takes the interface role. Components use the token for emphasis without a semantic element, such as the titles of search results.

<p>Load <strong>one</strong> stylesheet in the root layout.</p>

10Editorial content

Authored text goes in .formatted: content whose structure comes from its author rather than from the interface, such as rendered Markdown, documentation, a text about an artwork or a chat answer, even a single paragraph. The class adds the rhythm between blocks and the marks that only reading needs: list markers and indents, definition spacing, link underlines, quotation bars, figure caption spacing and code block surfaces. It inherits the typography of its role. Interface elements, such as navigation, cards, metadata lines and forms, are styled by their components and never take .formatted, even when their markup uses paragraphs or lists. Elements that look the same in both contexts, such as headings, tables and captions, take their document defaults without the class; cascade layers describe this division.

Three classes serve reading text with or without it: .constrained-width limits the line length, .underlined marks a link that color alone would not distinguish, and .scrollable contains a table wider than its column.

TokenRoleDefinition
--reading-max-widthMaximum line length of .constrained-width; ch follows the current font.65ch

See Classes for the complete class contract. The live example lets you change the reading measure independently of its alignment.

11Classes

NameDescription
.proseComplete typography for continuous authored writing: serif family and independent prose size, weight, line height and tracking. Inline code, kbd and samp are reduced once to 0.85em; nested literals inherit it.
.uiComplete interface typography: mono family, UI size, weight, line height and tracking, normal style and uppercase. Inherits color.
.metadataComplete typography for dates, authors, sources and references: mono family, independent metadata tokens, normal style, uppercase and muted color.
.codeComplete typography for code blocks and literal data: mono family, independent code tokens, normal style and source casing. Native pre blocks use it; code, kbd and samp share its family, weight, tracking, style and casing.
.formattedFormats authored text: block spacing, list markers and indents, definition spacing, underlined links, quotation bars, figure caption spacing and code block surfaces. Inherits typography and fills its column; adds no padding or maximum width.
.constrained-widthLimits the line length to --reading-max-width and fills the available width up to it, so a centering parent keeps the measure. Works alone or with .formatted.
.underlinedUnderlines text like the links of .formatted: a 1px line, offset in proportion to the text. Inherits color.
.scrollableScrolls content wider than its column, such as a wide table, horizontally inside the column. Give the element tabindex="0", role="region" and an accessible name so keyboard users can scroll it.
.typescaleRecomputes every role size from the local --font-size-base and applies the body size. Spacing is unchanged.

12Overrides

NameTypeDefaultDescription
--font-size-baseCSS length0.95rem at the rootReference length for all type sizes, 15.2px at a 16px root.
--reading-max-widthCSS length65chMaximum line length of .constrained-width; does not center the block or align its text.

13Notes

  • Load @dg/ui/styles.css once. Body text is sans-serif; native h1–h6 headings use the serif heading role and shared heading scale. An interface description takes .constrained-width alone for the reading measure.
  • Each role has its own size, weight, line-height and letter-spacing tokens; see the role sections above. Set role tokens on the intended scope; for a local body-size override, also resolve font-size: var(--body-font-size) there so descendants inherit it.
  • Use .prose for continuous writing by an author, such as a presentation or a text about an artwork. Interface descriptions, captions and short reading text keep the body role. Compose .formatted.prose.constrained-width for structured authored text.
  • Scope .ui to interface elements. A formatted block inside a .ui parent still inherits uppercase mono typography; .formatted does not reset that context. Do not combine .ui and .metadata.
  • When only the monospace family is needed, set font-family: var(--font-mono) in the owning component’s styles. Other text properties continue to inherit.
  • Use .metadata for short references such as dates and authors. Put URLs, titles of works and any reference whose casing matters in body text, since .metadata displays uppercase.
  • Form help, validation errors and status feedback use metadata typography through .feedback; see native fields. Do not set body typography on them.
  • Formatted blocks are separated by --content-gap-block; a following h2–h6 heading or hr, and the block after an hr, use one and a half times this gap, so the hierarchy follows any override. Lists and definitions indent by --space-5; an ordered list indents by --space-6 from its tenth item, or when its start is other than 1, so two-digit numbers stay inside the column. Unordered items show a muted cross centered on the first line, and ordered items restore their native numbers in the same --text-muted. Items are --space-2 apart; in a loose list, whose items wrap their content in paragraphs, items and their paragraphs are separated by the content gap, as quotation paragraphs are. Each definition term with its definitions is --space-2 from the next. A figure caption following its media sits --space-2 below it. Quotations and code blocks use content padding tokens. Code blocks sit on --background-subtle with a --border-subtle edge: the fill identifies them. Inline code keeps the surrounding text size and line rhythm. Long words wrap, and code blocks scroll inside the column, like every pre.
  • Outside formatted content, links are identified by their position, as in navigation. When a link, or a button presented as a text link, sits among plain text of the same style, add .underlined so color alone does not distinguish it.
  • Use the parent stack's items-center to center a constrained block. Text alignment belongs in local CSS. Keep formatted content separate from layout stacks so margins and gaps do not accumulate.
  • A class on an element inside formatted content overrides the formatted styles: .framed replaces the edge of a code block. See cascade layers.
  • In formatted content, long unbroken words and URLs wrap at any character. Table cells size their columns around whole words instead, so a long URL widens the table; wrap the table in .scrollable. For hyphenated breaks in reading text, add hyphens: auto in the owning styles; it follows the lang of the page.
  • Classes do not create semantics, render Markdown or sanitize HTML. Keep native headings, lists, buttons and links. Uppercase is visual; source strings remain unchanged.

14Usage

<div class="stack items-center">
  <article class="formatted constrained-width">
    <h2>About the work</h2>
    <p class="metadata">Artist name · 2026</p>
    <p>A first paragraph introducing the work.</p>
    <p>A second paragraph with a <a href="/artworks">collection link</a>.</p>
  </article>
</div>
<nav class="row control-gap ui" aria-label="Collection views">
  <a href="/artworks">Artworks</a>
  <a href="/about">About</a>
</nav>
<code style="font-family: var(--font-mono)">MixedCaseIdentifier</code>
<div class="formatted prose constrained-width">
  <p>A text written by the artist, set in the serif prose role.</p>
  <p>Its paragraphs, links and lists follow the formatted rhythm.</p>
</div>

15Examples

Change the base to scale every role, or override one role independently. Only this preview changes.

Heading · An exhibition

Reading text describes an artwork, explains a process or introduces a collection. Its size follows the body role.

Collection · Exhibitions · Archive

Below, the first paragraph inherits its serif, italic context; .ui and .metadata apply their role defaults.

Inherited typography · Gallery reference

.ui · Explore the collection

A shared reading rhythm

Editorial content can move from a short introduction to a longer account of an artwork. Both inherit the document typography, while headings retain their own hierarchy.

A second paragraph uses the shared content gap. Emphasis, italics and a link to the final note remain distinct.

Structure within the text
  • A paragraph can introduce a collection.
  • Lists can express a small hierarchy:
    1. Describe the work.
    2. Explain how it is experienced.
  • Long identifiers wrap without widening the page: collection_2026_distributed_protocol_documentation_reference.
  • A loose list wraps each item in paragraphs.

    Its items are separated like the paragraphs inside them, so each item still reads as one group.

  • A single paragraph keeps the same gap.

From the tenth item, numbers take a wider indent and stay inside the column:

  1. Title
  2. Artist
  3. Year
  4. Medium
  5. Dimensions
  6. Edition
  7. Provenance
  8. Exhibitions
  9. Literature
  10. Notes
Reading measure
The maximum line length of a constrained reading block.
Rhythm
The gaps between blocks, list items and definitions.

A clear structure lets the content set the pace.

The layout supports reading across different screen sizes.

Demonstration text · editorial quotation and caption
Works in the collection
WorkYearMedium
Chaos Machine2018Crypto-jukebox
One Arm Crypto Bandit2019Key-collision machine

Inline code such as --reading-max-width uses the monospace family. Longer code keeps its formatting and scrolls inside its own block.

<div class="stack items-center">
  <article class="formatted constrained-width">
    <p>A deliberately long code sample keeps its spaces and remains scrollable within the reading column.</p>
  </article>
</div>
A final note

The reading measure follows the typeface and size. The surrounding page can stay full width.

distributed·gallery