راهنمای تایپوگرافی فارسی
persian-typographyراهنماکدام فونت رایگانه و کدام لایسنس میخواد، اندازه و فاصلهی خط برای فارسی، اعداد در جدول، متن ترکیبی فارسی و انگلیسی و نیمفاصله در HTML. هم برای مدل، هم برای خودتون.
کی به کار میآد
- انتخاب و لود فونت در Next.js
- متن ترکیبی فارسی و لاتین
- جدولهای عددی و قیمت
- وقتی حروف به هم نمیچسبن یا اعداد لاتین میمونن
برچسبها
راهنما فرانتمتر نداره و خودکار فعال نمیشه؛ از CLAUDE.md یا AGENTS.md به آن لینک بدید تا مدل در هر جلسه بخوندش.
Persian typography guide (راهنمای تایپوگرافی فارسی)
A reference for humans and coding agents: which fonts to use and how to load them, sizes and spacing that read well in Persian, digits, mixed Persian and Latin text, and the CSS that keeps letters joined. Drop it in the repo and link it from CLAUDE.md or AGENTS.md, or read it once before styling a Persian interface.
1. Choosing a font
| Font | License | Where it fits | Notes |
|---|---|---|---|
| Vazirmatn | SIL OFL, free | UI, docs, body text | Nine weights, Latin included, on Google Fonts; the safe default. |
| Shabnam / Sahel / Samim | SIL OFL, free | Body and UI alternatives | Same author family as Vazir; good Persian digits; Latin is weaker. |
| IRANSans / IRANSansX | Commercial | Corporate and banking products | Needs a license per site; commonly requested by Iranian clients. |
| Yekan Bakh | Commercial | Product UI | Popular in startups; license required. |
| Dana | Commercial | Editorial and product | Wide weight range; license required. |
| Lalezar | SIL OFL, free | Display only | Headlines and posters; never body text. |
Rules:
One text family per product, one optional display family. Do not mix three.
Never fall back to Inter, Roboto, Geist or
system-uialone for Persian; they either lack the glyphs or shape them poorly and the browser mixes fonts mid-word.A font stack for Persian still needs a Latin-capable font for embedded Latin runs (Vazirmatn covers both; for others add a Latin font after it).
Ask the client whether they hold an IRANSans license before shipping it.
2. Loading fonts
Next.js with Google Fonts:
1// app/fonts.ts2import { Vazirmatn } from "next/font/google";34export const vazirmatn = Vazirmatn({5 subsets: ["arabic", "latin"],6 variable: "--font-vazirmatn",7 display: "swap",8});1// app/layout.tsx2<html lang="fa" dir="rtl" className={vazirmatn.variable}>1/* globals.css (Tailwind v4) */2@theme inline {3 --font-sans: var(--font-vazirmatn), "Vazirmatn", ui-sans-serif, system-ui, sans-serif;4}Self-hosted (IRANSans or any licensed font):
1import localFont from "next/font/local";23export const iranSans = localFont({4 src: [5 { path: "../fonts/IRANSansX-Regular.woff2", weight: "400" },6 { path: "../fonts/IRANSansX-Medium.woff2", weight: "500" },7 { path: "../fonts/IRANSansX-Bold.woff2", weight: "700" },8 ],9 variable: "--font-iransans",10 display: "swap",11});Ship
woff2; subset to the Arabic script block plus Latin if the tool allows.Preload only the regular weight; the rest can load with
swap.Three weights (400, 500, 700) cover almost every UI; add 600 for dense dashboards.
Set a metric-compatible fallback (
size-adjust,ascent-override) or accept a small layout shift; Persian fallback fonts differ a lot in height.
3. Sizes and spacing
| Element | Size | Line height |
|---|---|---|
| Body / UI | 15–17px (16px default) | 1.7–1.9 |
| Long reading | 17–18px | 1.9–2.0 |
| Small labels, captions | 12–13px | 1.6 |
| Headings | any | 1.2–1.35, never below 1.15 |
| Buttons, inputs | 14–16px (16px on iOS to avoid zoom) | 1 line, vertically centred |
Persian needs more line height than Latin: ascenders, descenders and dots stack above and below the baseline. 1.5 that looks fine in English is cramped in Persian.
letter-spacing: 0, always. Tracking breaks letter joining and changes the shape of words. Tailwind'stracking-tighton a Persian headline is a bug.Word spacing can go slightly up for justified text, but avoid
text-justifyin UI; usetext-start.No
uppercase,capitalizeor small-caps; they do nothing for Persian and look wrong on the Latin words next to it.Do not fake bold or italic (
font-synthesis); use shipped weights. Italic Persian barely exists; prefer weight or color for emphasis.
4. Digits
Persian digits ۰۱۲۳۴۵۶۷۸۹ (U+06F0–U+06F9) in everything the user reads. Arabic-Indic ٠١٢٣ (U+0660–U+0669) look different in ۴ ۵ ۶ and are wrong in Persian text.
Convert at the display layer (
fa()from VibeFarsilib/utils.ts); keep Latin digits in values, URLs, codes and version numbers.Tables and prices:
font-variant-numeric: tabular-nums(tabular-numsin Tailwind) so columns line up.Thousands «٬» (U+066C), decimal «٫» (U+066B), percent «٪» (U+066A) after the number: «۱۲٬۴۵۰٫۵ تومان»، «۲۰٪».
Phone, card and IBAN digits are Persian on screen, Latin in the value, and the control is
dir="ltr".
5. Mixed Persian and Latin
The bidi algorithm handles a Latin word inside a Persian sentence («با Next.js ساختیم») without help.
Codes, SKUs, URLs and file names inside Persian text go in
<bdi>or an element withdir="ltr", so punctuation and digits do not jump:<bdi dir="ltr">SKU-2048</bdi>.Never set
dir="ltr"on a whole Persian paragraph to "fix" one token.Latin brand names keep their case; do not translit unless the brand does.
Parentheses and quotes around Latin runs: use Persian «گیومه» outside, Latin quotes inside if needed.
6. ZWNJ and spacing in HTML
ZWNJ (نیمفاصله, U+200C) joins compounds visually without a space: میشود، کتابها، بزرگتر. In HTML write the character or
‌.Search and copy: ZWNJ is a real character; normalize it when matching user input (
replace(//g, "")), keep it when rendering.Tailwind
whitespace-nowrapandbreak-wordsare fine;break-allis not (it splits joined letters).Truncate with
line-clamp-*ortruncate; neverslice()a Persian string for display.Punctuation hugs the word: no space before «،» «؟» «!» «؛», one space after.
7. Alignment and direction inside components
text-startnottext-right; components then work in both directions.Icons next to text:
gap-2in a flex row, no margin-left/right classes.Number and text in the same cell:
tabular-numson the number,dir="auto"on the cell if content can be either script.Inputs: the field is RTL by default; set
dir="ltr"only for Latin-shaped values.
8. Quick checks
Zoom the page to 200%: are dots and letters still attached? If not, a fallback font is rendering somewhere; check
font-familyon that element.Select a word: does the whole word highlight as one run? Broken joining means ZWNJ or letter-spacing problems.
Compare a price column: do the digits line up? If not, add
tabular-nums.Look at a headline with
tracking-tight: remove it.Open on a phone: inputs at 16px, body at 16px, line height ≥ 1.7.
نمونه
پرامپت: «تیتر صفحهی اصلی را بزرگ و فشرده کن»
بدون مهارت
className="text-5xl tracking-tight uppercase"
با مهارت
className="text-5xl leading-[1.2]" و فونت وزن ۷۰۰ بدون letter-spacing
نمونه برای نشان دادن جهت تغییره؛ خروجی واقعی به مدل و پرامپت شما بستگی داره.
نصب
با CLI
این دستور فایل را در
docs/persian-typography.mdمینویسه. CLI به init نیاز نداره؛ فقط باید داخل پوشهی پروژه باشید.$npx vibefarsi add persian-typographyدستی
محتوای تب persian-typography.md را کپی کنید و در مسیر ابزار خودتون بگذارید:
- Claude Codeفایل را در
docs/persian-typography.mdبگذارید و این خط را بهCLAUDE.mdاضافه کنید:@docs/persian-typography.md - Cursorدر
.cursor/rules/persian-typography.mdcباalwaysApply: trueبالای فایل - Codex و بقیهمتن را در
AGENTS.mdبگذارید یا از همانجا به فایل لینک بدید
کپی کل فایلregistry json- Claude Codeفایل را در
میخواید همهی قوانین را یکجا داشته باشید؟ قوانین فارسی برای CLAUDE.md خلاصهی همهی مهارتها در یک صفحهست و قوانین کرافت رابط طرف طراحی را پوشش میده. npx vibefarsi init هر دو را داخل پروژه مینویسه.