PVR Tech Studio

Typography

The type system and its showcase page at /ui/typography — the heading scale, body/article text, the type-scale reference table, and the skin-aware font utilities (.font-display, .font-data, .font-numeral) that read the --font- tokens so type re-skins with the active design and localizes for CJK languages.

6 min read
Updated July 15, 2026

Overview

Typography in Luminaux is token-driven, exactly like color. Three CSS custom properties define the faces (--font-sans, --font-display, --font-mono), three utility classes expose them (.font-display, .font-data, .font-numeral), and everything else uses Tailwind's built-in type scale (text-xs … text-4xl, font-*, tracking-*, leading-*). Because the faces are tokens, a skin or a full design (Bento, Console) can swap the display/mono faces, and a language (Japanese, Chinese) can swap the whole stack — with no component changes.

/ui/typography (src/pages/ui/TypographyPage.tsx) is the live reference for this system: it renders the heading scale, display type, body copy, lists, a long-form article sample, the font faces each skin uses, and a type-scale table. It's a showcase, not a reusable component — the reusable parts are the tokens and the three utilities documented here.

Architecture & files

FileResponsibility
src/styles/index.cssDefines --font-sans / --font-display / --font-mono (base + skin + html[lang] overrides) and the .font-display / .font-data / .font-numeral utilities.
src/pages/ui/TypographyPage.tsxThe /ui/typography showcase — HEADINGS and SCALE data arrays + the section panels.
src/styles/_skin-*.scssPer-design font overrides (e.g. Console → JetBrains Mono, Bento keeps the default display face).
index.htmlLoads the web fonts (Plus Jakarta Sans, Inter, JetBrains Mono; Noto Sans JP/SC on demand for CJK).
src/locales/<lng>/demo.jsonThe showcase page's section labels + sample copy (typography* keys).

Usage

Use Tailwind's type utilities for size/weight/spacing, and the three font utilities when you need a specific face:

// A display heading (uses the skin's display face)
<h2 className="font-display text-2xl font-bold tracking-tight text-foreground">Revenue</h2>
 
// A numeric readout with column-aligned digits
<span className="font-data text-lg text-foreground">$48,290.00</span>
 
// Body copy inherits --font-sans automatically — no utility needed
<p className="text-sm text-muted-foreground">Standard body text.</p>

API / Props

Font tokens

Defined in src/styles/index.css and mapped into Tailwind via @theme inline:

TokenBase value (default skin)Purpose
--font-sansPlus Jakarta Sans, system fallbacksThe base UI font — applied to <body>.
--font-displayInter, system fallbacksHeadings / display text; skins may override.
--font-monoJetBrains Mono / ui-monospace fallbacksMonospace / numeric-data readouts.

Font utilities

UtilityMaps toUse for
.font-displayvar(--font-display)Headings / display text. Follows the active skin's display face.
.font-datavar(--font-mono) + tabular-nums + tight letter-spacingNumeric/data readouts where digits must align in columns (KPI values, tables).
.font-numeralJetBrains Mono (fixed, skin-independent)Oversized numerals that must stay monospace regardless of skin (e.g. error-page status codes).

The type scale

The /ui/typography scale table (SCALE in TypographyPage.tsx) documents the sizes the template uses, via Tailwind's default scale:

ClassSizeTypical use
text-xs12pxCaptions, meta labels
text-sm14pxBody / UI default
text-base16pxLong-form reading
text-lg18pxLeads / subheads
text-2xl24pxSection titles
text-4xl36pxHero / page titles

Weights and tracking follow Tailwind (font-medium/font-semibold/font-bold, tracking-tight on large display text). No custom scale config is needed — Tailwind v4 is config-less here.

Configuration & customization

Per-skin & per-design fonts

A color skin is token-only and normally keeps the default faces. A full design may also swap fonts: Console uses JetBrains Mono throughout (tight radius, terminal feel), while Bento keeps the default display face. These overrides live in the design's _skin-*.scss partial (or the skin block in index.css) by redefining --font-display / --font-mono under the [data-skin="…"] selector. See Design Skins.

CJK language overrides

For Japanese and Chinese, the entire font stack swaps to a CJK face so glyphs render correctly, via html[lang] overrides in index.css:

html[lang="ja"] { --font-sans: 'Noto Sans JP', 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif; /* + display/mono */ }
html[lang="zh"] { --font-sans: 'Noto Sans SC', 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif; /* + display/mono */ }

All other languages keep the default faces. The CJK subsets download on demand. See i18n.

The showcase page sections

/ui/typography presents these sections (each a Panel with a title + description):

SectionShows
HeadingsThe heading scale (HEADINGS data) with size badges.
DisplayLarge .font-display display type.
Motion textThe animated-text FX (Typewriter, ScrambleText, RotatingWord) applied to type.
BodyParagraph copy, inline code, emphasis.
ListsOrdered / unordered list styling.
ArticleA long-form article sample (h1/h2 + paragraphs) for reading rhythm.
FontsThe .font-display and .font-data faces the active skin resolves to.
ScaleThe type-scale reference table (SCALE).

Examples

A KPI tile mixing display and data faces (values stay column-aligned across rows):

<div className="rounded-xl border border-border bg-surface p-4">
    <p className="text-xs text-muted-foreground">Monthly revenue</p>
    <p className="font-data text-2xl font-semibold text-foreground">$48,290.00</p>
    <p className="font-display text-sm text-muted-foreground">vs last month</p>
</div>

An oversized status numeral that must stay monospace regardless of skin (error pages):

<span className="font-numeral text-[8rem] leading-none text-primary">404</span>

Best practices

  • Never hardcode a font family in a component — use .font-display / .font-data / .font-numeral or the --font-* tokens, so type re-skins and localizes automatically.
  • Use .font-data for numbers in tables/KPIs so digits align (tabular-nums).
  • Reserve .font-numeral for the rare case where a numeral must stay monospace even when a skin overrides --font-mono.
  • Size with Tailwind's scale (text-*, font-*, tracking-*); don't introduce custom pixel sizes.
  • Color type with tokens (text-foreground, text-muted-foreground) — never raw palette or hex.

Troubleshooting

SymptomLikely causeFix
Headings don't change with the Console designHardcoded font family instead of .font-displayUse the utility / --font-display
Numbers misalign in a tableUsing the default sans instead of .font-dataApply .font-data to numeric cells
Japanese text renders with tofu / wrong glyphshtml[lang] not set, or Noto font not loadedEnsure the language switch sets <html lang> (it does via i18n)
A numeral changed face under a skinUsed .font-data where you meant skin-independentUse .font-numeral for fixed-monospace numerals

FAQ

Why is /ui/typography not a reusable component? It's a showcase of the type system. The reusable pieces are the tokens and the three utilities — those are what you compose in real pages.

Can I add a font weight or face? Load it in index.html and point a --font-* token (or a skin's override) at it. Keep it in the token layer so skins/CJK stay consistent.

Where does the showcase copy live? In the demo i18n namespace (typography* keys in src/locales/<lng>/demo.json).

Notes for designers & content editors

  • Faces are tokens. Change --font-sans / --font-display / --font-mono in src/styles/index.css (or a skin block) — never inline a font-family in a component.
  • Sample copy on the showcase page is i18n text (demo:typography*), safe to edit; letterform specimens and font names stay literal.
  • CJK is handled automatically per <html lang> — you don't add per-string overrides.

Was this page helpful?