Skip to main content
View as Markdown

Typography

theme.fonts maps a role to a font configuration. Each role key generates a --font-{role} CSS custom property and a matching font-{role} utility, so type decisions live in config rather than scattered across className strings.

app.yaml
theme:
  fonts:
    heading:
      family: Inter
      fallback: 'system-ui, sans-serif'
      letterSpacing: '-0.02em'
    body:
      family: Inter
      fallback: 'system-ui, sans-serif'
    mono:
      family: 'JetBrains Mono'
      fallback: 'ui-monospace, monospace'

Role keys are alphabetic and freely chosen. heading, body and mono are the conventional three — Sovrium's prebuilt components look for them — but a design that needs display or caption simply declares it.

Font Properties

Property Description
family Required. Font family name, e.g. Inter, Roboto.
fallback Fallback stack used until the family loads, or if it never does.
style normal (default), italic, or oblique.
letterSpacing CSS value, e.g. 0.05em, -0.01em.
transform none, uppercase, lowercase, or capitalize.
url Font file or Google Fonts URL, injected as a <link> in the document head.
weights Superseded. See below.
size Superseded. See below.
lineHeight Superseded. See below.

Loading a Web Font

Give the role a url and Sovrium injects the stylesheet link for you. Nothing else is required — no manual <head> entry, no @font-face block.

app.yaml
theme:
  fonts:
    body:
      family: Inter
      fallback: 'system-ui, sans-serif'
      url: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap'

Self-hosted fonts work the same way — point url at a stylesheet in your public directory. That keeps the request on your own origin, which matters when a third-party font CDN is not acceptable under your privacy posture.

What no longer reaches the browser

Three of the fields above validate and then reach nothing. They are documented as superseded rather than removed, because they still decode — but declaring one has no effect on what renders.

Field What actually happens
lineHeight Nothing at all. No CSS custom property is emitted for it.
size Reaches only the legacy hero section renderer, as an inline font size. It never becomes a CSS variable, and it does not set the document's base font size.
weights Only the first entry is read, and only by that same renderer. It never reaches an @font-face rule, so weights: [300, 400, 700] loads no additional font file.

Declare sizes, leading and weight in design.typeScale instead, where each step emits a real custom property and a usable text-{step} utility:

app.yaml
design:
  theme:
    fonts:
      body:
        family: Inter
        fallback: 'system-ui, sans-serif'
  typeScale:
    body:
      size: '1rem'
      lineHeight: 1.6
      weight: 400
      font: body

To load several weights of a font, request them in the url — a Google Fonts stylesheet takes wght@400;600 — or point url at a self-hosted stylesheet declaring the @font-face rules you need.

sovrium validate prints a Superseded: notice naming each declared path and exits 0. The three fields are removed at the next major, alongside the top-level theme alias.

Last updated September 1, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium