Files
nick-doc/05 - Design System/Typography.md
2026-05-23 20:35:34 +03:30

187 lines
5.3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
<Typography variant="h1" sx={{ fontFamily: '"Barlow", serif' }}>Welcome</Typography>
```
---
## 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 100900. 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 (h1h2): `-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
<Typography variant="body1" sx={{ fontVariantNumeric: 'tabular-nums' }}>
{amount}
</Typography>
```
---
## 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 **4575 characters per line** on body copy. Constrain with `maxWidth`:
```tsx
<Typography variant="body1" sx={{ maxWidth: 720 }}>{longBody}</Typography>
```
---
## 8. Text truncation
Single-line:
```tsx
<Typography noWrap>{text}</Typography>
```
Multi-line (clamp at 2 lines):
```tsx
<Typography
sx={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>{text}</Typography>
```
---
## 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 `<bdi>` 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 `<link rel="preload" as="font" />` to the root layout.
---
## 11. Customising
Add a font:
1. `yarn add @fontsource-variable/<name>`
2. `import '@fontsource-variable/<name>'` 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]]