AI now writes most interface code, and it is trained overwhelmingly on left-to-right English UI — so it reflexively hardcodes margin-left, text-align: left, and physical directions that silently break the moment content becomes Arabic, Hebrew, Persian, or Urdu. Right-to-left (RTL) and bilingual interfaces are not “LTR, flipped”: they demand correct use of CSS Logical Properties, the Unicode Bidirectional Algorithm, and the dir/lang attributes, plus dozens of typographic subtleties that generic code gets wrong. This is a concrete, testable checklist for building RTL and Arabic/bilingual interfaces that are actually correct.
Layout & direction
- Set direction with the HTML
dirattribute (dir="rtl",dir="ltr", ordir="auto"), not the CSSdirectionproperty — the attribute is semantic and is respected by form controls and the bidi algorithm. - Put
dirandlangon the<html>element for the primary language, then override per-element only where a sub-tree switches direction. - Treat
dirandlangas independent: an English string in an Arabic page keepslang="en"but may sit in an RTL flow, and Arabic UI around LTR data keepsdir="rtl"withlang="ar". - Never derive one from the other in code — set both
dirandlangexplicitly, because Arabic embedded in an LTR document exists and vice-versa. - Use
dir="auto"on any element whose text is user-generated or unknown at build time (names, comments, queries) so the browser picks direction from the first strong character. - Design the whole layout to mirror: reading order, navigation, progress and hierarchy flow from the inline-start (right, in RTL), not a fixed left edge.
- Style direction-dependent rules with
:dir(rtl)/:dir(ltr)or[dir="rtl"]selectors instead of duplicating whole stylesheets. - Do not build a separate "RTL stylesheet" of physical-property overrides — author one stylesheet in logical properties so it mirrors automatically.
- Never let the page scroll horizontally: mirroring a fixed-width LTR layout that assumed a left origin is the most common source of RTL overflow.
Logical CSS
- Use
margin-inline-start/margin-inline-end, nevermargin-left/margin-right. - Use
padding-inline-start/padding-inline-endinstead ofpadding-left/padding-right. - Use
inset-inline-start/inset-inline-end(andinset-block-*) for positioned elements instead ofleft/right. - Use
border-inline-start/border-inline-endfor edge borders such as active indicators, quote bars and dividers. - Use the logical border-radius longhands (
border-start-start-radius, etc.) so rounded corners follow direction. - Use
text-align: startandtext-align: end, nevertext-align: left/right. - Use
float: inline-start/inline-endinstead offloat: left/right. - Prefer flow-relative shorthands (
inset,margin-block,padding-inline) and setdirectionat the container so children inherit correct logical mapping. - If a value genuinely must be physical (e.g. a drop-shadow offset), handle it explicitly per direction rather than assuming it mirrors.
Typography — Arabic & bilingual
- Never apply
letter-spacingto Arabic, Persian or Urdu — it breaks the cursive joining and produces disconnected glyphs. - Increase
line-heightfor Arabic (roughly 1.5–2.0): its ascenders, deep descenders and stacked diacritics need vertical room. - Size Arabic body text slightly larger than the equivalent Latin — its letterforms lose legibility faster at small sizes.
- Do not use
text-transform: uppercase/capitalizeon Arabic — the script is unicameral and the property is meaningless or harmful. - Never synthesize italic/oblique on Arabic — it has no italic tradition; use weight, colour or a different family for emphasis.
- Avoid faux-bold on Arabic — prefer a real bold weight, since synthetic bolding smears joined strokes.
- Set
langcorrectly on each run so shaping, diacritic positioning and locale rules apply. - Reserve line-box height for tashkeel/harakat when present, or they clip against the line above — test with fully-voweled text.
- Do not strip tashkeel where meaning or pronunciation depends on it (names, learner content, scripture).
Bidirectional text (bidi)
- Trust the Unicode Bidirectional Algorithm (UAX #9) for ordering within a run, but give it the correct paragraph base direction.
- Isolate every embedded run of opposite or unknown direction with
<bdi>orunicode-bidi: isolate. - Wrap interpolated variables of unknown direction (names, titles, search terms) in
<bdi>to prevent spillover reordering. - Use
unicode-bidi: plaintext(ordir="auto") for multi-paragraph user content so each paragraph gets its own base direction. - Insert an RLM (U+200F) or LRM (U+200E) to fix stray neutral characters (punctuation, brackets) that land on the wrong side.
- Prefer the isolate controls — LRI/RLI/FSI (U+2066–2068) closed by PDI (U+2069) — over the deprecated embeddings when composing bidi strings in code.
- Never build a sentence by concatenating translated fragments around a variable and hoping the order holds — insert explicit isolates.
- Type the logical
(not a visually-swapped one — paired brackets are mirrored automatically by the bidi algorithm in RTL context.
Numerals & digits
- Decide numeral system by locale, not direction: Arabic-script pages may use European (0–9), Arabic-Indic (U+0660–U+0669) or Eastern/Persian digits depending on region.
- Do not hardcode one numeral system for "Arabic" — the Maghreb largely uses European digits while much of the Mashriq uses Arabic-Indic.
- Render numbers with
Intl.NumberFormatand an explicitnumberingSystemrather than mapping code points by hand. - Digit runs always lay out left-to-right internally, even inside RTL text — never reverse digits yourself.
- Keep the minus sign, decimal and thousands separators correct for the locale, not blindly
.and,. - Keep phone numbers, card numbers and other digit sequences as LTR runs (
dir="ltr"or<bdi>) or separators land in the wrong order. - Do not let a leading number set base direction via
dir="auto"— numbers are neutral, so set direction explicitly for numeric fields.
Icons & mirroring
- Mirror directional icons in RTL: back/forward and next/previous arrows, chevrons, reply/send, undo/redo, indent/outdent.
- Mirror affordances that imply reading direction: bullets/indentation, breadcrumb separators, disclosure carets, start-filled progress, tooltip tails.
- Do NOT mirror media playback controls — the play triangle maps to a universally LTR timeline.
- Do NOT mirror clocks or clockwise motion (timers, refresh/reload arrows) — time direction is universal.
- Do NOT mirror checkmarks — a checkmark is a symbol, not a directional arrow.
- Do NOT mirror icons with intrinsic form (hourglass, musical note, camera, phone) or embedded text/numerals.
- Flip with
transform: scaleX(-1)scoped under:dir(rtl), or ship a mirrored asset — never hand-edit the base icon to work one way. - Audit each icon against "does its meaning depend on reading direction?" — mirror only if yes; when unsure, verify with native users.
Forms & inputs
- Let the input's
dirfollow the content: an Arabic form still needsdir="ltr"on email, URL, username and numeric fields. - Set
dir="auto"on free-text inputs that accept either script so caret, alignment and typed text follow the first strong character. - Align field text and placeholders to the logical start with
text-align: start— never hardcodetext-align: left. - Ensure placeholder direction matches the field's
dir, or it renders on the wrong edge. - Put required asterisks, currency prefixes and unit suffixes on the correct logical side with inline-start/inline-end.
- Keep search icons, clear buttons and dropdown carets on the correct edge with
inset-inline-end, notright. - Set
type="tel",type="email",type="url"and numeric inputs to LTR entry even in RTL forms — their values are inherently LTR.
Dates, times, currency, units
- Format all dates and times with
Intl.DateTimeFormatand an explicit locale — never by string concatenation. - Support alternate calendars where expected (e.g.
islamic-umalqura) instead of assuming Gregorian. - Let the locale decide day/month/year order and 12/24-hour convention.
- Format currency with
Intl.NumberFormat({style:'currency'})so symbol choice, placement, digits and separators follow the locale. - Do not assume the currency symbol goes on the left — in many Arabic locales it follows the amount.
- Render currency and unit strings as cohesive isolated runs so they don't split across a bidi boundary.
- Display clocks and countdowns with digits in LTR order even in Arabic UI.
Fonts
- Choose a font that genuinely supports Arabic shaping (initial/medial/final/isolated forms) and required ligatures — many Latin fonts render Arabic as broken, unjoined glyphs.
- Provide an Arabic-capable font for every surface via
lang-scoped rules orunicode-range, so Arabic never falls back to a Latin-only face. - Verify the font includes the mandatory lam-alef ligature and correct tashkeel positioning before shipping.
- Match Arabic and Latin fonts for weight and optical size in bilingual UI so mixed lines look balanced.
- Keep the OpenType features Arabic needs enabled (
liga,calt,mark,init,medi,fina,isol) — never disablecalt/liga. - Ensure numeral glyphs exist in the numbering system you render, or numbers fall back to a clashing font.
Motion & scroll
- Mirror the direction of motion: "next" slides, carousels and drawers move from the inline-start toward the end edge.
- Do not animate with a fixed-sign
translateX— flip the sign under:dir(rtl), sincetranslateXis physical and won't mirror. - Mirror swipe and drag gestures so "back" and "dismiss" go the culturally correct way.
- Set the scroll origin correctly — horizontal containers and snap points must treat the inline-start (right) as zero in RTL.
- Mirror horizontal progress bars, timelines and loaders to fill from the start edge.
- Flip drop shadows, directional gradients and light-source effects intentionally per direction —
box-shadowandlinear-gradient(to right,…)do NOT auto-mirror.
Testing & tooling
- Test with real Arabic/Hebrew content, not "Lorem ipsum" or reversed Latin — pseudo-RTL misses shaping, joining and numeral bugs.
- Test the hardest case: a mixed string (Arabic sentence containing an English brand name, a URL, a phone number and a price) and confirm nothing reorders wrongly.
- Verify with fully-voweled Arabic to catch line-height clipping and diacritic collisions.
- Toggle
dirat the root and confirm the whole UI mirrors from one logical stylesheet — anything that stays put is using a physical property. - Audit every icon against the mirror/no-mirror exceptions list.
- Validate with a screen reader in Arabic to confirm reading order,
langand announced direction. - Add automated checks: lint CSS to ban physical properties and markup for missing
dir/lang.
Common AI-generated mistakes
- Do not emit
margin-left/padding-right/left:/text-align: leftby default — AI-written CSS defaults to physical LTR properties that never mirror. - Do not "flip to RTL" by wrapping everything in
transform: scaleX(-1)— it reverses text, images and icons into a mirror world. - Do not set
direction: rtlin CSS while leavingdiroff the markup — controls and the bidi algorithm rely on the attribute. - Do not concatenate translated strings around variables without bidi isolation — the classic name/number-jumps-to-the-wrong-side bug.
- Do not hardcode European digits for Arabic — pick the numeral system by locale via
Intl. - Do not add
letter-spacingto "improve" Arabic headings — it disconnects the cursive script. - Do not
text-transform: uppercaseArabic labels copied from an English design system — the script has no case. - Do not blindly mirror every icon — AI over-corrects and flips play buttons, clocks and checkmarks that must stay put.
- Do not leave
type="tel"/email/urlinputs in the page's RTL direction — their values are LTR.
These rules are largely mechanical and can be enforced automatically — lint your CSS for physical properties, your markup for missing
dir/lang, and your strings for un-isolated bidi runs (e.g. with Miraat) so RTL correctness is verified in CI, not discovered by users.Automate it
Check these rules
in CI. Get rtlint.→
Our open-source linters catch most of these automatically, before your users do.
Explore the tools →