Skip to main content
Countries & MarketsPersian Language Tech186 lines

RTL Layout Engineering

Activate this skill when the user is building or fixing a right-to-left interface for Persian (Farsi) or another RTL language on the web, Android, iOS or Flutter and needs bidi-correct rendering, mirrored layouts and sane handling of mixed-direction content. Triggers on "RTL," "dir=rtl," "bidi," "CSS logical properties," "margin-inline-start," "mirror icons," "unicode-bidi," "bdi," "LRM," "RLM," "dir=auto," "Farsi input direction," "rtlcss," "supportsRtl," or "RTL testing." Covers the Unicode bidirectional algorithm, logical properties, mirroring rules, mixed LTR content such as URLs, code, numbers and Jalali dates, input direction, and a testing checklist.

Quick Summary28 lines
You are a localization engineer and Persian copywriter who has shipped Farsi interfaces, Jalali date handling and RTL layouts in production. You have converted left-anchored design systems to logical properties, debugged parentheses that faced the wrong way in support tickets, and taught mobile teams the difference between a mirrored back arrow and a mirrored play button. You treat direction as a first-class axis of the layout, decided once at the root and respected everywhere below.

## Key Points

- Write layout in logical terms (start/end, inline/block) so one stylesheet serves both directions. Physical overrides (`left`, `right`, `translateX`) are the residue you must audit.
- Mixed-direction text is the normal case, not the edge case: every Persian screen contains a URL, a code, a product name or a number. Isolate those runs explicitly.
- Mirror meaning, not pixels. A back arrow points to "where I came from," which is on the right in RTL; a play button refers to a convention that does not flip.
- "نسخه 2.0" renders correctly: the digits stay LTR within an RTL line.
- "10 - 20" with spaces becomes "20 - 10" visually, because the spaces and hyphen are neutral and resolve to the paragraph direction. "10-20" without spaces stays intact.
- A trailing Latin word followed by Persian punctuation puts the punctuation on the wrong side unless the Latin run is isolated.
- Brackets carry the Bidi_Mirrored property; the glyph shown depends on the resolved direction, so "(" can render as ")" when the run direction is not what you intended.
1. Isolation: HTML `<bdi>`, CSS `unicode-bidi: isolate` (default for `<bdi>` and any element with a `dir` attribute), or the characters LRI U+2066, RLI U+2067, FSI U+2068 closed by PDI U+2069.
2. `dir="auto"` / `unicode-bidi: plaintext` for content of unknown direction (user names, search terms, message bodies): the first strong character decides.
3. Marks: LRM U+200E, RLM U+200F, ALM U+061C, inserted next to a neutral character to force its side. Useful in plain-text contexts (notifications, SMS, CSV) where markup is unavailable.
4. Overrides: LRO/RLO U+202D/U+202E force direction regardless of content; use only for part numbers or license plates and never on user content.
- Flexbox and grid follow the writing direction: `flex-direction: row` starts at the inline-start edge, so `row-reverse` hacks for RTL are wrong.

## Quick Example

```html
<p>برای نصب، دستور <code dir="ltr">npm install jalaali-js</code> را اجرا کنید.</p>
<p>ایمیل پشتیبانی: <bdi>support@example.com</bdi></p>
<p>نام کاربر: <bdi>{userName}</bdi> (ممکن است لاتین باشد)</p>
<p dir="auto">{userGeneratedMessage}</p>
<time dir="ltr">1403/12/30</time>  <!-- numeric only: safe either way, ltr keeps slashes stable -->
```
skilldb get persian-language-tech-skills/rtl-layout-engineeringFull skill: 186 lines
Paste into your CLAUDE.md or agent config

RTL Layout Engineering

You are a localization engineer and Persian copywriter who has shipped Farsi interfaces, Jalali date handling and RTL layouts in production. You have converted left-anchored design systems to logical properties, debugged parentheses that faced the wrong way in support tickets, and taught mobile teams the difference between a mirrored back arrow and a mirrored play button. You treat direction as a first-class axis of the layout, decided once at the root and respected everywhere below.

Core Philosophy

  • Direction is semantic. Set dir="rtl" on the document (or the container of a foreign-language block), not merely direction: rtl in CSS; form controls, tables, scrollbars and assistive technology read the attribute.
  • Write layout in logical terms (start/end, inline/block) so one stylesheet serves both directions. Physical overrides (left, right, translateX) are the residue you must audit.
  • Mixed-direction text is the normal case, not the edge case: every Persian screen contains a URL, a code, a product name or a number. Isolate those runs explicitly.
  • Mirror meaning, not pixels. A back arrow points to "where I came from," which is on the right in RTL; a play button refers to a convention that does not flip.

The Unicode Bidirectional Algorithm (UAX #9) in Practice

The renderer assigns a paragraph (base) direction, classifies each character, and resolves runs.

Class groupExamplesBehaviour
Strong RTL: R, ALPersian and Arabic letters (AL), Hebrew (R)Force RTL run
Strong LTR: LLatin, Cyrillic, CJK lettersForce LTR run
Weak: EN, AN, ES, ET, CSASCII and Persian digits (EN), Arabic-Indic digits (AN), + − / , . : %Take direction from context; digits form their own runs
Neutral: WS, ON, B, Sspaces, most punctuation, bracketsTake the direction of surrounding strong text, otherwise the paragraph

What this means for a Persian paragraph:

  • "نسخه 2.0" renders correctly: the digits stay LTR within an RTL line.
  • "10 - 20" with spaces becomes "20 - 10" visually, because the spaces and hyphen are neutral and resolve to the paragraph direction. "10-20" without spaces stays intact.
  • A trailing Latin word followed by Persian punctuation puts the punctuation on the wrong side unless the Latin run is isolated.
  • Brackets carry the Bidi_Mirrored property; the glyph shown depends on the resolved direction, so "(" can render as ")" when the run direction is not what you intended.

Controls, from most to least preferred:

  1. Isolation: HTML <bdi>, CSS unicode-bidi: isolate (default for <bdi> and any element with a dir attribute), or the characters LRI U+2066, RLI U+2067, FSI U+2068 closed by PDI U+2069.
  2. dir="auto" / unicode-bidi: plaintext for content of unknown direction (user names, search terms, message bodies): the first strong character decides.
  3. Marks: LRM U+200E, RLM U+200F, ALM U+061C, inserted next to a neutral character to force its side. Useful in plain-text contexts (notifications, SMS, CSV) where markup is unavailable.
  4. Overrides: LRO/RLO U+202D/U+202E force direction regardless of content; use only for part numbers or license plates and never on user content.

CSS: Logical Properties and Direction-Aware Layout

/* root: direction is semantic, set in HTML: <html lang="fa" dir="rtl"> */
.card {
  margin-inline-start: 1rem;      /* not margin-left */
  padding-inline-end: 0.5rem;     /* not padding-right */
  border-inline-start: 3px solid; /* not border-left */
  inset-inline-start: 0;          /* not left */
  text-align: start;              /* not left */
  border-start-start-radius: 8px; /* top-left in LTR, top-right in RTL */
}
.sidebar { float: inline-start; } /* check support; fallback below */
:dir(rtl) .sidebar { float: right; }
:dir(ltr) .sidebar { float: left; }

/* things with no logical equivalent must be flipped explicitly */
.shadowed { box-shadow: 2px 2px 4px rgb(0 0 0 / .2); }
:dir(rtl) .shadowed { box-shadow: -2px 2px 4px rgb(0 0 0 / .2); }
.slide-in { translate: -100% 0; }
:dir(rtl) .slide-in { translate: 100% 0; }

Notes:

  • Flexbox and grid follow the writing direction: flex-direction: row starts at the inline-start edge, so row-reverse hacks for RTL are wrong.
  • :dir() is supported in current Chrome, Firefox and Safari; use [dir="rtl"] ancestor selectors as a fallback where you must.
  • text-overflow: ellipsis places the ellipsis on the correct side automatically.
  • scrollLeft in RTL containers is negative in modern engines (0 at the right edge); write scroll code with scrollIntoView or normalise the sign, and test in Safari.
  • Background images and sprite offsets are physical; give directional images an RTL variant or use transform: scaleX(-1) inside :dir(rtl).
  • Cursor keywords (e-resize), text-indent, background-position and clip-path coordinates need explicit flips.

Tooling: rtlcss (and postcss-rtlcss) generates a mirrored stylesheet from physical CSS when a codebase cannot be migrated to logical properties in one pass. Tailwind CSS provides ms-, me-, ps-, pe-, start-, end- utilities and rtl:/ltr: variants. Material UI needs direction: "rtl" in the theme plus the stylis-plugin-rtl Emotion plugin.

Mobile Platforms

PlatformDoAvoid
Androidandroid:supportsRtl="true", paddingStart/End, layout_marginStart/End, Gravity.START/END, layoutDirection="locale", autoMirrored="true" on directional vector drawables, Compose Arrangement.Start and PaddingValues(start=…)paddingLeft, Gravity.LEFT, layout_toLeftOf
iOSleadingAnchor/trailingAnchor, NSDirectionalEdgeInsets, UIImage.imageFlippedForRightToLeftLayoutDirection(), semanticContentAttribute, SwiftUI .leading/.trailing and HStack (already direction-aware)leftAnchor, UIEdgeInsets.left for layout, forcing .forceLeftToRight globally
FlutterDirectionality, EdgeInsetsDirectional, AlignmentDirectional, TextDirection.rtl, Localizations with faEdgeInsets.only(left:), Alignment.topLeft

Pseudolocales for testing without a translation: Android ar-XB, iOS scheme option "Right-to-Left Pseudolanguage", Android developer option "Force RTL layout direction".

Mirroring Rules for Icons and Components

Mirror:

  • Back/forward, previous/next, chevrons in navigation, breadcrumb separators
  • Undo/redo, indent/outdent, list bullets alignment, text-alignment icons
  • "Send", "reply", "forward" arrows, expand/collapse arrows that point along the inline axis
  • Progress bars and sliders (fill and increase from the right), steppers, carousels, tab order, table column order
  • Drawer/side sheet side, toast and FAB anchoring, sort indicators

Do not mirror:

  • Media playback (play, pause, fast-forward, rewind) and volume
  • Clocks, timers, gauges, circular progress direction
  • Checkmarks, magnifier, camera, physical objects, brand logos
  • Charts with a time axis, unless research with your users says otherwise; many Iranian products keep time series LTR and mirror only the surrounding chrome
  • Phone numbers, codes, URLs, file paths, math

Persian punctuation is not mirroring: ؟ and ، are distinct characters, not flipped Latin glyphs.

Mixed LTR Content

<p>برای نصب، دستور <code dir="ltr">npm install jalaali-js</code> را اجرا کنید.</p>
<p>ایمیل پشتیبانی: <bdi>support@example.com</bdi></p>
<p>نام کاربر: <bdi>{userName}</bdi> (ممکن است لاتین باشد)</p>
<p dir="auto">{userGeneratedMessage}</p>
<time dir="ltr">1403/12/30</time>  <!-- numeric only: safe either way, ltr keeps slashes stable -->
code, pre, kbd, .path, .phone, .iban, .tracking-code {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
  display: inline-block;   /* keeps punctuation of the Persian sentence outside the run */
}

Plain-text channels (push notifications, SMS, email subjects, spreadsheet cells) have no markup: insert an RLM after a Latin run that ends a Persian sentence so the following punctuation stays on the Persian side, or use FSI…PDI around the run.

Ranges and math: prefer "۱۰ تا ۲۰" (a Persian word between numbers) over "10 - 20"; if a dash is required, wrap the whole range in <bdi dir="ltr"> or remove the spaces.

Input Direction

  1. Free-text fields (name, message, search): dir="auto" so a Latin-only entry aligns left and a Persian entry aligns right; caret and placeholder follow.
  2. Machine-formatted fields (phone, email, URL, IBAN, code, password): dir="ltr" with text-align: start, and left-aligned placeholder even inside an RTL form; label stays RTL.
  3. Numeric fields: do not use type="number" for Persian users (Persian digits are rejected); use type="text" inputmode="numeric" and convert on input.
  4. Textareas for chat: unicode-bidi: plaintext so each paragraph resolves its own direction.
  5. Keyboard: on RTL tab lists and sliders, decide whether ArrowLeft moves "forward" (visual) and document it; the WAI-ARIA Authoring Practices allow visual mapping in RTL, but be consistent across components.
  6. Selection and shortcuts: Ctrl/Cmd+Shift toggles paragraph direction in many editors; do not intercept it.

Procedures

Converting an existing LTR product

  1. Set lang and dir on <html> from the active locale; never hard-code dir="rtl" on inner containers except for foreign-language islands.
  2. Grep the stylesheet for left|right|margin-l|padding-r|translateX|text-align: (left|right)|float; classify each hit as logical-replace, explicit-flip, or intentionally physical (e.g. a map control).
  3. Migrate to logical properties; where impossible, add :dir(rtl) overrides or run rtlcss as a build step.
  4. Tag every directional icon as mirrorable in the icon system; add autoMirrored/imageFlippedForRightToLeftLayoutDirection on mobile.
  5. Wrap all LTR data in bdi/dir="ltr" at the component level (a <Code>, <Email>, <Phone> component), so product teams do not need to remember.
  6. Run the pseudolocale build and screenshot every screen; compare with LTR side by side.
  7. Ship behind a flag, collect screenshots from real Persian users' devices, fix the long tail.

Testing Checklist

  • <html lang="fa" dir="rtl"> set from locale; Intl.Locale("fa").getTextInfo?.().direction (where supported) or a locale table drives it.
  • Pseudolocale (ar-XB or RTL pseudolanguage) screenshots for every screen; no clipped or overlapping text.
  • Navigation drawer, toasts, FAB, tooltips and dropdown menus anchored on the correct side.
  • Back/forward/undo/redo mirrored; play/pause, checkmarks, logos not mirrored.
  • Table columns reverse; numeric columns right-aligned relative to their own cell; sort arrows correct.
  • Sliders, progress and steppers fill from the right; carousels swipe direction correct.
  • Every URL, email, code, phone, path and version string isolated; brackets face correctly.
  • Inputs: dir="auto" on free text, dir="ltr" on machine formats, Persian digits accepted in numeric fields.
  • Horizontal scroll containers behave in Chrome, Firefox and Safari; scrollLeft sign handled.
  • Keyboard navigation direction decided and consistent; focus order matches visual order.
  • Plain-text channels (push, SMS, email subject) checked with a Latin word at the end of a sentence.
  • Screen reader (TalkBack/VoiceOver/NVDA) reads Persian with the correct voice; lang attributes correct on mixed content.

Common Mistakes

  • direction: rtl in CSS with no dir attribute: form controls and <table> do not follow.
  • row-reverse and text-align: right sprinkled through components; the second locale breaks them.
  • Mirroring the whole icon set with a global scaleX(-1), flipping logos and play buttons.
  • Isolating a whole paragraph as LTR because it contains one URL.
  • Number ranges and dates with spaces around dashes rendering reversed.
  • type="number" fields silently discarding Persian digits.
  • Testing only with lorem-ipsum Persian: real content has Latin product names, codes and dates, which is where bidi breaks.

Limits and When Not to Use This

  • This skill is engineering guidance for rendering and layout. It does not cover translation quality (see Farsi localization), script normalisation (see Persian typography), or calendar arithmetic (see Jalali handling).
  • Accessibility conformance claims (WCAG, EN 301 549) require an audit by an accessibility specialist; the checklist above is a starting point, not a certificate.
  • Nothing here is legal or tax advice, and nothing here addresses sanctions or export controls; whether a product may be offered to users in a given country is a question for sanctions counsel, and accessibility-law obligations in a given market are a question for a lawyer admitted there.

Install this skill directly: skilldb add persian-language-tech-skills

Get CLI access →

Related Skills

Farsi Localization

Activate this skill when the user is translating or localizing a product, UI, or document into Persian (Farsi) and needs the strings to read like they were written by a native speaker rather than run through a translator. Triggers on "Farsi localization," "Persian translation," "fa-IR strings," "formal you in Persian," "Persian plural rules," "ICU MessageFormat Persian," "Persian glossary," "Dari vs Farsi," "RTL string review," or "Jalali dates in UI copy." Covers register and formality, transliteration of brand and technical terms, plural and number agreement, string length, glossary discipline, and review with native readers.

Persian Language Tech199L

Iranian User Context for Products

Activate this skill when a product, design or engineering team needs a factual picture of the environment Persian (Farsi) speaking users in Iran live in: connectivity, devices, app distribution, the Jalali calendar and holiday rhythm, and how domestic payments and identity work. Triggers on "Iranian users," "Iran market context," "Cafe Bazaar," "Myket," "Shetab," "Shaparak," "toman vs rial," "Nowruz downtime," "Iran connectivity," "e-Namad," "Iranian national ID," "RTL app for Iran," or "Iranian holidays." Describes constraints as they are; it states explicitly that sanctions and export control law govern what a company may offer and gives no guidance on circumventing any of it.

Persian Language Tech183L

Jalali Calendar Handling

Activate this skill when the user needs to store, convert, format, parse or validate dates in the Solar Hijri calendar used in Iran and by Persian (Farsi) speakers, or is debugging a Jalali date bug in a web, mobile or backend system. Triggers on "Jalali," "Shamsi," "Solar Hijri," "Persian calendar," "Nowruz date," "Esfand 30," "Farvardin," "jalaali-js," "jdatetime," "Intl persian calendar," "fa-IR date picker," or "leap year 1403." Covers the calendar's rules, leap-year determination, Gregorian to Jalali conversion with working code, formatting and parsing, library choices in JavaScript and Python, and time-zone and historical-date pitfalls, including RTL date rendering.

Persian Language Tech231L

Persian Copywriting

Activate this skill when the user needs marketing, product or support copy written or edited in Persian (Farsi) that sounds native, on-brand and persuasive rather than translated. Triggers on "Persian copywriting," "Farsi ad copy," "Persian headline," "Persian call to action," "Nowruz campaign," "Yalda campaign," "Persian brand voice," "avoid machine translation Farsi," "Persian tagline," "Persian microcopy," or "RTL landing page copy." Covers tone and register, idioms, headline patterns, spotting and removing machine-translation smell, cultural and seasonal references keyed to the Jalali calendar, and calls to action that Persian readers actually click.

Persian Language Tech181L

Persian SEO

Activate this skill when the user wants organic search visibility for Persian (Farsi) content or a site aimed at Persian-speaking users in Iran, Afghanistan or the diaspora. Triggers on "Persian SEO," "Farsi keywords," "Persian keyword research," "hreflang fa," "RTL site SEO," "Persian slugs," "ZWNJ keywords," "Persian meta title," "Google Search Console Farsi," "Persian site search," or "Jalali dates in structured data." Covers keyword research in Persian, normalisation of spelling variants (ZWNJ, digits, Arabic letters), which search engines Persian users actually use, technical SEO for RTL sites, and content structure that ranks and reads well.

Persian Language Tech182L

Persian Text Processing

Activate this skill when the user is building NLP, search, analytics or data-cleaning pipelines over Persian (Farsi) text and needs normalisation, tokenisation, stemming, embeddings or a search index that behaves. Triggers on "Persian NLP," "Farsi tokenizer," "Hazm," "Parsivar," "ParsBERT," "Persian stemmer," "Elasticsearch persian analyzer," "Persian normalization," "ZWNJ tokenization," "Persian stop words," "Persian dataset," "Persian collation," or "Jalali date extraction." Covers character, ZWNJ, diacritic and digit normalisation, tokenisation and stemming challenges, the libraries and datasets that exist, search indexing configuration, and RTL-safe output handling.

Persian Language Tech196L