--- title: Typography tags: [design-system, typography, fonts] created: 2026-05-23 --- # Typography The system uses **Public Sans Variable** as the primary face with **Barlow** as a secondary (display) face, plus locale-specific Persian/Arabic faces loaded when the active language requires them. --- ## 1. Font stack Loaded via `@fontsource-variable` (variable fonts streamed at build) plus `@fontsource/barlow`. Confirm in `frontend/package.json`: ```jsonc "@fontsource-variable/public-sans": "^5.2.5", // Primary "@fontsource-variable/dm-sans": "^5.2.5", // Optional preset "@fontsource-variable/inter": "^5.2.5", // Optional preset "@fontsource-variable/nunito-sans": "^5.2.5", // Optional preset "@fontsource/barlow": "^5.2.5", // Secondary (display) ``` Imported in `frontend/src/app/layout.tsx` (or a fonts module) so Next can fingerprint and preload them. Default font-family stack in the theme: ```css font-family: "Public Sans Variable", "Helvetica", "Arial", sans-serif; ``` Display-only headings (banners, hero) may override with Barlow via the `sx` prop: ```tsx Welcome ``` --- ## 2. Type scale | Variant | Size (px) | Line height | Weight | Use | |---|---|---|---|---| | `h1` | 64 | 80 | 800 | Hero titles only | | `h2` | 48 | 64 | 800 | Page titles | | `h3` | 32 | 48 | 700 | Section titles | | `h4` | 24 | 36 | 700 | Card titles, dialog headers | | `h5` | 20 | 30 | 700 | Sub-section titles | | `h6` | 18 | 28 | 700 | Item titles, sidebar headers | | `subtitle1` | 16 | 24 | 600 | Form section labels | | `subtitle2` | 14 | 22 | 600 | List item subtitles | | `body1` | 16 | 24 | 400 | Body copy default | | `body2` | 14 | 22 | 400 | Secondary copy, table cells | | `caption` | 12 | 18 | 400 | Helper text, timestamps | | `overline` | 12 | 18 | 700 | Tags, all-caps category labels | | `button` | 14 | 24 | 700 | Button text — NOT uppercase | > [!tip] > Use `responsiveFontSizes(theme)` once at theme creation to scale `h1`/`h2` on small screens automatically. --- ## 3. Weights The variable Public Sans face supports 100–900. Convention: | Weight | Token | Use | |---|---|---| | 400 | `fontWeightRegular` | Body | | 500 | `fontWeightMedium` | Mild emphasis | | 600 | `fontWeightSemiBold` | Subtitles, table headers | | 700 | `fontWeightBold` | Headings | | 800 | (raw) | Display | Never use `font-weight: bolder` — always pick from the table. --- ## 4. Letter-spacing - Headings (h1–h2): `-1` (tight, modern) - Overline: `+1.1` (open, all-caps) - Body, subtitle: `0` --- ## 5. Numeric variants Tabular figures matter for financial UI (payments, prices). Enable per-instance: ```tsx {amount} ``` --- ## 6. Multi-script support The system officially supports Latin, **Persian (Farsi)**, and **Arabic** rendering; French and Vietnamese use Latin glyphs already covered by Public Sans; Chinese (Simplified) falls back to the OS-default CJK font (the current fontsource set does not ship CJK). Persian/Arabic font swap is layered at theme build: ```ts fontFamily: locale === 'fa' || locale === 'ar' ? '"Vazirmatn", "Public Sans Variable", "Tahoma", sans-serif' : '"Public Sans Variable", "Helvetica", sans-serif' ``` > [!note] > "Vazirmatn" is the **recommended** Persian face for new installations — it has full Unicode coverage and a variable axis. If not installed via `@fontsource`, ship it as a self-hosted woff2 under `public/fonts/`. For Chinese (`cn`), consider adding `"PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC"` to the family at the front. For Vietnamese (`vi`) — Public Sans already covers all needed glyphs. --- ## 7. Line-length Aim for **45–75 characters per line** on body copy. Constrain with `maxWidth`: ```tsx {longBody} ``` --- ## 8. Text truncation Single-line: ```tsx {text} ``` Multi-line (clamp at 2 lines): ```tsx {text} ``` --- ## 9. Bidi & RTL considerations - Don't hard-code `text-align: left/right` — use `text-align: start/end` so it flips with direction. - For inline mixed-script (Persian + English), prefer the auto direction inside `` or wrap with the Unicode `‫` (RLE) / `‬` (PDF) markers. - Numeric formatting per locale handled by `Intl.NumberFormat`; do NOT translate digits manually. --- ## 10. Loading & FOUC prevention - All fonts loaded at the route segment that needs them (Next.js best practice). - `font-display: swap` is the default in fontsource — visible text uses fallback first, then swaps. - For the FIRST PAINT on auth pages, preload the primary Latin face by adding `` to the root layout. --- ## 11. Customising Add a font: 1. `yarn add @fontsource-variable/` 2. `import '@fontsource-variable/'` in `src/app/layout.tsx`. 3. Add to `fontFamily` stack in `theme/options/typography.ts`. 4. Register as a settings option if user-selectable. --- ## Related - [[Theme Configuration]] · [[Design System Overview]] · [[Colors]] - [[Internationalization & RTL]] - [[Settings & Theming]]