رابط کاربری راستبهچپ RTL UI
ساخت رابط کاربری فارسی و راستبهچپ: ویژگیهای منطقی CSS، فونت فارسی، متن دوجهته، ارقام و آینه کردن آیکونها
مهارتتوسعه نرمافزار
پیش از نصب بدانید
- منبعساخت بازارچهنوشته و نگهداریشده در همین مخزن
- کد منبعمتنباز، در همین مخزنآخرین بررسی: ۱۱ مهر ۱۴۰۵
نصب رابط کاربری راستبهچپ
Claude Code با افزونه
یک بار بازارچه را اضافه کنید، بعد هر مهارت را جدا نصب کنید.
/plugin marketplace add mcp-farsi/mcp-farsi
/plugin install rtl-ui@mcp-farsiنصب دستی در پوشه مهارتها
برای یک پروژه خاص، به جای ~/.claude از .claude در ریشه پروژه استفاده کنید. در PowerShell به جای curl بنویسید curl.exe و به جای ~ بنویسید $HOME.
curl -fsSL --create-dirs -o ~/.claude/skills/rtl-ui/SKILL.md {ORIGIN}/skills/rtl-ui/SKILL.mdClaude.ai و اپ دسکتاپ
فایلهای مهارت را در پوشهای به نام rtl-ui بگذارید، آن را zip کنید و در تنظیمات Claude، بخش Capabilities، بارگذاری کنید.
درباره
بیشتر کدی که مدلها برای رابط کاربری مینویسند چپبهراست فرض شده است: margin-left به جای margin-inline-start، فلشهایی که جهتشان برعکس است و شماره تلفنی که وسط متن فارسی بههم میریزد. این مهارت چکلیست ساخت رابط فارسی درست را در اختیار Claude میگذارد.
کجا به کار میآید
- ساخت صفحه یا کامپوننت تازه با HTML، CSS، React یا Tailwind
- فارسیسازی یک رابط انگلیسی موجود
- رفع مشکل متنهای دوجهته، ارقام و ورودیهای فرم
متن کامل مهارت
نمایش محتوای SKILL.md
رابط کاربری راستبهچپ (RTL Persian UI)
1. Document setup
<html lang="fa" dir="rtl">
- Put
dirin markup on the root element, not only CSSdirection. The attribute drives the bidi algorithm, form controls,:dir()and accessibility;lang="fa"lets screen readers pick a Persian voice. - Multilingual app: set both from the active locale (
document.documentElement.lang = 'fa'; document.documentElement.dir = 'rtl'). - Mark LTR islands locally with
dir="ltr"(section 5).
2. Layout with logical properties
Write direction-agnostic CSS so one stylesheet serves RTL and LTR.
| Physical (avoid) | Logical (use) |
|---|---|
margin-left / margin-right |
margin-inline-start / margin-inline-end |
padding-left / padding-right |
padding-inline-start / padding-inline-end |
left: 0 / right: 0 |
inset-inline-start: 0 / inset-inline-end: 0 |
text-align: left / right |
text-align: start / end |
border-left |
border-inline-start |
border-top-left-radius |
border-start-start-radius |
- Flexbox (
flex-direction: row) and Grid columns already followdir: in RTL the first item is on the right. Do not addrow-reversefor RTL; that flips it back. justify-content: flex-start/startfollow the direction; the keywordsleft/rightdo not.- Things that do not flip automatically:
transform: translateX(),background-position,box-shadowoffsets, gradients withto left/right, absolutely positioned SVG, canvas drawing, animation keyframes. Override them with:dir(rtl)(Baseline widely available since December 2023):
.drawer { transform: translateX(-100%); }
.drawer:dir(rtl) { transform: translateX(100%); }
- Horizontal scrollers: in RTL,
scrollLeftis0at the start (rightmost position) and becomes increasingly negative as the user scrolls toward the end. Carousel code that assumes positive values breaks.
3. Icons: what to mirror
Mirror (they express direction, motion or reading order):
- Back / forward arrows, next / previous chevrons, breadcrumb separators, “open submenu” carets. In RTL, a back button points right.
- Progress bars, sliders, steppers (and the order of the numbers along them).
- Icons that represent lines of text or list alignment.
- Icons showing forward motion, for example a speaker with sound waves.
Do not mirror:
- Media playback controls (play, pause, fast-forward, rewind).
- Checkmarks, close (X), and other universal or direction-neutral symbols.
- Clocks and other real-world objects; logos (never flip a logo); code icons such as
</>. - The slash in “disabled / prohibited” variants stays the same.
Flip only icons you mark as directional; never flip all icons globally:
.icon-directional:dir(rtl) { transform: scaleX(-1); }
4. Bidi: mixed Persian, English and numbers
Typical bugs: a trailing ! or . jumps to the wrong side, React 19 or an English user name reorders the words around it, a phone number shows up reversed in groups.
- Text injected at run time whose direction you cannot predict (user names, product titles, search terms): wrap it in
<bdi>. It isolates the text and picks a direction from its first strong character.
<p>کاربر <bdi>{{ username }}</bdi> ۳ دیدگاه نوشت.</p>
- Blocks of user-generated content (comments, posts, chat messages) and free-text inputs and textareas:
dir="auto". - Any element with a
dirattribute is isolated from surrounding text. In CSS, useunicode-bidi: isolateon inline badges or chips, andunicode-bidi: plaintexton blocks where each paragraph should pick its own direction. - Prefer markup over Unicode control characters. Use the isolate characters FSI U+2068 … PDI U+2069 (or LRM U+200E / RLM U+200F) only where markup is impossible:
<title>, attribute values (title,placeholder,aria-label),<option>text, notifications, SMS. In code, write them as escapes (\u2068,\u2069), never as invisible literals.
5. LTR islands
These always stay LTR, and the digit order inside a number never reverses:
- Phone and card numbers, IBAN (شبا), tracking codes, OTP codes.
- Emails, URLs, file paths, code snippets, version strings, keyboard shortcuts.
<span dir="ltr">0912 345 6789</span>
<input type="tel" dir="ltr" inputmode="tel" autocomplete="tel">
<input type="email" dir="ltr">
<pre dir="ltr"><code>npm install vazirmatn</code></pre>
A Persian placeholder in an LTR input aligns left. Optional fix:
input[dir="ltr"]:placeholder-shown { direction: rtl; }
6. Persian fonts
| Font | License | Notes |
|---|---|---|
| Vazirmatn | SIL OFL 1.1 | First choice. npm vazirmatn, or Fontsource @fontsource-variable/vazirmatn. Variable font; extra builds in the repo (UI, Farsi-Digits, Non-Latin) |
| Sahel | SIL OFL 1.1 | rastikerdar/sahel-font |
| Shabnam | SIL OFL 1.1 | Repository archived (discontinued); prefer Vazirmatn for new work |
- Self-host the font files.
fonts.googleapis.comhas been slow or disrupted from inside Iran (Iranian hosts reported ISP disruption starting 1401) and a failed font request falls back silently. jsDelivr and other foreign CDNs carry the same risk; servewoff2from your own origin or a domestic CDN.
@font-face {
font-family: Vazirmatn;
src: url('/fonts/Vazirmatn[wght].woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}
:root { font-family: Vazirmatn, Tahoma, sans-serif; }
- Preload the main weight on the critical path (
<link rel="preload" as="font" type="font/woff2" crossorigin>). - “Farsi-Digits” font builds draw Latin
0-9with Persian shapes, but copy-paste still yields Latin digits. Prefer real Persian digits in the text (section 8) and a standard build.
7. Typography
- line-height: Persian has dots and marks above and below the letters; it needs more leading than Latin. Start around 1.8 for body text and tune by eye.
- letter-spacing: normal for Persian. CSS Text 3 forbids letter-spacing from breaking the joins of cursive scripts, but browsers differ: recent Chrome and Firefox skip spacing between joined letters, while WebKit (Safari) has been reported to space every glyph, and the words fall apart. Scope tracking to Latin only, for example
:lang(en). - No
text-transform: uppercase/capitalizeand no small-caps. Persian has no letter case, so these do nothing to Persian and only distort embedded Latin (brand names, acronyms). Remove them from RTL styles. - Persian next to uppercase Latin at the same size can look small; a slightly larger Persian size can balance it.
- Vazirmatn has no italic; browsers fake one by slanting the glyphs. Use bold for emphasis.
8. Numbers
- Display:
(1234567.5).toLocaleString('fa-IR')returns۱٬۲۳۴٬۵۶۷٫۵(Persian digits, ٬ thousands, ٫ decimal).(0.5).toLocaleString('fa-IR', {style: 'percent'})returns۵۰٪. - Toman is not an ISO 4217 currency (IRR, Rial, is). Most shops show Toman: format the number and append the unit yourself,
${n.toLocaleString('fa-IR')} تومان. - Keep Latin digits for values the user copies into another system (card numbers, IBAN, tracking codes), and in code, URLs and data sent to APIs.
9. Input handling
Users type Persian digits (Persian keyboard) or Arabic-Indic digits (Arabic keyboards, some Android keyboards). Normalize to ASCII before validation and storage.
- JS:
/\d/matches only ASCII digits andNumber('۱۲۳')isNaN, so unnormalized input fails validation. - Python:
\dmatches Persian digits andint('۱۲۳')returns123, so unnormalized input passes validation and leaks into storage. Normalize anyway, or usere.ASCII.
// Persian (U+06F0-06F9) and Arabic-Indic (U+0660-0669) digits -> ASCII
export const toEnDigits = (s) =>
s.replace(/[\u06F0-\u06F9\u0660-\u0669]/g, (d) => String(d.charCodeAt(0) & 0xf));
const mobile = toEnDigits(input.value.trim());
/^(?:\+98|0098|0)?9\d{9}$/.test(mobile); // Iranian mobile: 09xxxxxxxxx, +989xxxxxxxxx
- Use
type="text"withinputmode="numeric"(or"tel") for numeric fields, so you can normalize before validating. - Search: normalize Arabic ي/ك to Persian ی/ک (see the persian-writing skill), and treat ZWNJ, space and nothing as equivalent when matching (
میشود,می شود,میشود). - Never strip ZWNJ (U+200C) from names or text fields; it is part of correct spelling.
10. Tailwind CSS
- Prefer logical utilities:
ms-*/me-*,ps-*/pe-*,border-s/border-e,rounded-s-*/rounded-e-*,text-start/text-end. For inset, current docs useinset-s-*/inset-e-*; Tailwind v3 usesstart-*/end-*. - Use
gap-*instead ofspace-x-*for horizontal spacing; gap does not depend on direction. - Use the
rtl:/ltr:variants only for what logical utilities cannot express, for example flipping a directional icon:rtl:-scale-x-100. - Replace
ml-*,mr-*,pl-*,pr-*,left-*,right-*,text-left,text-rightin RTL-capable code.
11. RTL test checklist
-
<html lang="fa" dir="rtl">is set; switching the rootdirmirrors every page (nav, forms, tables, modals, toasts, drawers, carousels, breadcrumbs, pagination). - No physical properties left. Search, for example:
rg -n "(margin|padding|border)-(left|right)|text-align:\s*(left|right)|\b(left|right):" src/ - Directional icons are mirrored; media controls, checkmarks and logos are not.
- Mixed strings render correctly:
نسخهٔ React 19 منتشر شد!, an English user name inside a Persian sentence,۵ GB, a URL at the end of a sentence followed by a period. - Phone numbers, codes and emails display LTR; their inputs are
dir="ltr". - Inputs accept Persian and Arabic-Indic digits and store ASCII; ZWNJ survives saving.
- Fonts still load with
fonts.googleapis.comblocked (DevTools request blocking). - No
letter-spacingortext-transformon Persian text; checked in Safari as well. - Screen reader reads Persian content with a Persian voice (
lang="fa").
