راهنمای تایپوگرافی فارسی

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

FontLicenseWhere it fitsNotes
VazirmatnSIL OFL, freeUI, docs, body textNine weights, Latin included, on Google Fonts; the safe default.
Shabnam / Sahel / SamimSIL OFL, freeBody and UI alternativesSame author family as Vazir; good Persian digits; Latin is weaker.
IRANSans / IRANSansXCommercialCorporate and banking productsNeeds a license per site; commonly requested by Iranian clients.
Yekan BakhCommercialProduct UIPopular in startups; license required.
DanaCommercialEditorial and productWide weight range; license required.
LalezarSIL OFL, freeDisplay onlyHeadlines 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-ui alone 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

ElementSizeLine height
Body / UI15–17px (16px default)1.7–1.9
Long reading17–18px1.9–2.0
Small labels, captions12–13px1.6
Headingsany1.2–1.35, never below 1.15
Buttons, inputs14–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's tracking-tight on a Persian headline is a bug.

  • Word spacing can go slightly up for justified text, but avoid text-justify in UI; use text-start.

  • No uppercase, capitalize or 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 VibeFarsi lib/utils.ts); keep Latin digits in values, URLs, codes and version numbers.

  • Tables and prices: font-variant-numeric: tabular-nums (tabular-nums in 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 with dir="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 &zwnj;.

  • Search and copy: ZWNJ is a real character; normalize it when matching user input (replace(/‌/g, "")), keep it when rendering.

  • Tailwind whitespace-nowrap and break-words are fine; break-all is not (it splits joined letters).

  • Truncate with line-clamp-* or truncate; never slice() a Persian string for display.

  • Punctuation hugs the word: no space before «،» «؟» «!» «؛», one space after.

7. Alignment and direction inside components

  • text-start not text-right; components then work in both directions.

  • Icons next to text: gap-2 in a flex row, no margin-left/right classes.

  • Number and text in the same cell: tabular-nums on 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

  1. Zoom the page to 200%: are dots and letters still attached? If not, a fallback font is rendering somewhere; check font-family on that element.

  2. Select a word: does the whole word highlight as one run? Broken joining means ZWNJ or letter-spacing problems.

  3. Compare a price column: do the digits line up? If not, add tabular-nums.

  4. Look at a headline with tracking-tight: remove it.

  5. 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

نمونه برای نشان دادن جهت تغییره؛ خروجی واقعی به مدل و پرامپت شما بستگی داره.

نصب

  1. با CLI

    این دستور فایل را در docs/persian-typography.md می‌نویسه. CLI به init نیاز نداره؛ فقط باید داخل پوشه‌ی پروژه باشید.

    $npx vibefarsi add persian-typography
  2. دستی

    محتوای تب 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.md خلاصه‌ی همه‌ی مهارت‌ها در یک صفحه‌ست و قوانین کرافت رابط طرف طراحی را پوشش میده. npx vibefarsi init هر دو را داخل پروژه می‌نویسه.