> ## Documentation Index
> Fetch the complete documentation index at: https://substrate.docs.unknowncreatives.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuring Type Scale and Font Families in Substrate

> Substrate has no type-size ladder. A base size and a scale ratio feed a continuous power function that semantic text roles sample at fractional exponents.

Typography in Substrate is continuous. There is no set of named size steps to pick from — no `xs` through `3xl`. You declare a base size and a modular ratio, and every text size in the system is that base raised to a power of the ratio, evaluated in CSS at use time so it tracks the user's type-scale preference without a rebuild.

## The typography block

An example, from the fictional Acme brand's `config.global.yaml`:

```yaml theme={null}
typography:
  heading-family: "'Acme Sans', 'Inter', system-ui, sans-serif"
  body-family: "'Acme Sans', 'Inter', system-ui, sans-serif"
  base-font-size: 1
  scale-ratio: 1.25     # major third — editorial scale
  fluid:
    rate: 0.5
  density:
    font-size-k: 0
    font-weight-k: 0
```

### `heading-family` and `body-family`

Required font stacks, as full CSS font-family strings. They reach the browser as `--font-heading` and `--font-body` on the brand selector, and components resolve through `--surface-font-family`. A family can share one stack and let a sub-brand diverge on a single field — the fictional Aurora Airways family might pair `"'Aurora Sans', Verdana, system-ui, sans-serif"` for both, with its Rewards sub-brand overriding only the heading stack to a condensed cut.

### `mono-family`

Optional. When omitted, the generated CSS falls back to a system monospace stack (`ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`).

### `base-font-size`

A number in **rem**, not a pixel string. Every bundled demo brand uses `1`, which emits `--font-size-base: 1rem`. This is the size of the baseline text role, and every other size is derived from it.

### `scale-ratio`

The modular ratio the scale raises to a power. `1.25` (major third) suits an editorial scale, `1.2` (minor third) a denser transactional one, and a data-heavy sub-brand might tighten further to `1.15`. This is the authored ratio — the effective one is computed at runtime.

### `fluid`

Optional responsive behavior. `rate` is the viewport or container coefficient used in the `clamp()` preferred value; `floor` and `ceiling` bound how far a size may shrink or grow as ratios of the computed size; `quantum` sets the rounding step in rem. Per-role fluid envelopes additionally carry absolute `min` and `max` in rem and can choose `reference: viewport` or `container`.

### `density`

Optional signed coefficients linking type to the density slider. `font-size-k` controls how much `--density` pushes font size, and `font-weight-k` how much it pushes weight. The bundled demo brands all set both to `0`, which decouples type from density — but a brand that wants text to tighten alongside spacing can dial them up.

## The continuous scale

The authored `scale-ratio` is not used directly. The runtime first blends it toward 1 by the user's type-scale preference, producing an effective ratio, then raises that to the role's exponent. From `generated/global/css/tokens.gen.css`:

```css theme={null}
:root {
  --effective-ratio: calc(1 + (var(--scale-ratio) - 1) * var(--type-scale-factor));
}
```

Every size in the system then comes from one expression, evaluated per element:

```css theme={null}
--surface-font-size: round(nearest,
  calc(var(--font-size-base) * pow(var(--effective-ratio), var(--_font-scale))),
  var(--font-size-quantum, 0.25rem));
```

The consequence is that a brand's typographic personality is two numbers. Raising `scale-ratio` from 1.2 to 1.25 widens the gap between every pair of adjacent roles at once, and a user who prefers larger type moves `typeScaleFactor` up — bounded by `flexibility.type-scale`, which every bundled demo brand sets to `{ min: 0.9, max: 1.4 }` — and the whole hierarchy expands proportionally without a regenerate.

## Semantic text roles

What replaces named size steps is a small set of semantic roles, each with a **fractional** exponent into the scale. From `generated/global/typescript/text-roles.gen.ts`:

| Role      | Font scale       | Font weight |
| --------- | ---------------- | ----------- |
| `heading` | 3                | 700         |
| `body`    | 0 (the baseline) | 400         |
| `caption` | -0.75            | 400         |
| `label`   | -0.5             | 600         |
| `code`    | -0.25            | 400         |
| `kbd`     | -0.5             | 600         |

Fractional exponents are the point. `caption` at -0.75 sits three-quarters of a step below body — a position no integer ladder can express. Each role also carries a fluid envelope with absolute rem bounds; `body`, for instance, is bounded to 0.875–1.5rem with a floor of 0.85 and a ceiling of 1.1.

Roles compose with the density coefficients at the component level. A badge, for example, sets its own scale offset and lets the brand's coefficients modulate it:

```css theme={null}
--_font-scale: calc(-1 + var(--density-fs-k, 0) * var(--density, 1));
--surface-font-weight: calc(600 + var(--density-fw-k, 0) * var(--density, 1));
```

<Note>
  Fluid sizing needs no viewport configuration block — there is no such block in the schema. The brand-level `fluid` settings plus each role's own envelope are the whole mechanism.
</Note>

<Tip>
  When a heading feels wrong, reach for `scale-ratio` before reaching for a role. The ratio moves the entire hierarchy coherently; a one-off role adjustment moves one thing and creates a new exception to maintain.
</Tip>

<CardGroup cols={2}>
  <Card title="Spacing & Density" icon="layout" href="/brand-config/spacing-density">
    The same continuous treatment applied to space — one unit, scaled at each use site.
  </Card>

  <Card title="Brand Config Overview" icon="sliders" href="/brand-config/overview">
    The full schema and the kebab-case normalization rule.
  </Card>
</CardGroup>
