5.3 KiB
title, tags, created
| title | tags | created | |||
|---|---|---|---|---|---|
| Typography |
|
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:
"@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:
font-family: "Public Sans Variable", "Helvetica", "Arial", sans-serif;
Display-only headings (banners, hero) may override with Barlow via the sx prop:
<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 scaleh1/h2on 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:
<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:
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 underpublic/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:
<Typography variant="body1" sx={{ maxWidth: 720 }}>{longBody}</Typography>
8. Text truncation
Single-line:
<Typography noWrap>{text}</Typography>
Multi-line (clamp at 2 lines):
<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— usetext-align: start/endso 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: swapis 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:
yarn add @fontsource-variable/<name>import '@fontsource-variable/<name>'insrc/app/layout.tsx.- Add to
fontFamilystack intheme/options/typography.ts. - Register as a settings option if user-selectable.