Home  /  Tools  /  Guidelines
Open reference · Free

RTL & Bilingual
Interface Guidelines

The reference for building interfaces that stay correct in every language and direction. 92 concrete, testable rules.

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 dir attribute (dir="rtl", dir="ltr", or dir="auto"), not the CSS direction property — the attribute is semantic and is respected by form controls and the bidi algorithm.
  • Put dir and lang on the <html> element for the primary language, then override per-element only where a sub-tree switches direction.
  • Treat dir and lang as independent: an English string in an Arabic page keeps lang="en" but may sit in an RTL flow, and Arabic UI around LTR data keeps dir="rtl" with lang="ar".
  • Never derive one from the other in code — set both dir and lang explicitly, 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, never margin-left / margin-right.
  • Use padding-inline-start / padding-inline-end instead of padding-left / padding-right.
  • Use inset-inline-start / inset-inline-end (and inset-block-*) for positioned elements instead of left / right.
  • Use border-inline-start / border-inline-end for 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: start and text-align: end, never text-align: left / right.
  • Use float: inline-start / inline-end instead of float: left / right.
  • Prefer flow-relative shorthands (inset, margin-block, padding-inline) and set direction at 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-spacing to Arabic, Persian or Urdu — it breaks the cursive joining and produces disconnected glyphs.
  • Increase line-height for 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 / capitalize on 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 lang correctly 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> or unicode-bidi: isolate.
  • Wrap interpolated variables of unknown direction (names, titles, search terms) in <bdi> to prevent spillover reordering.
  • Use unicode-bidi: plaintext (or dir="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.NumberFormat and an explicit numberingSystem rather 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 dir follow the content: an Arabic form still needs dir="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 hardcode text-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, not right.
  • 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.DateTimeFormat and 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 or unicode-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 disable calt/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), since translateX is 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-shadow and linear-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 dir at 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, lang and 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: left by 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: rtl in CSS while leaving dir off 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-spacing to "improve" Arabic headings — it disconnects the cursive script.
  • Do not text-transform: uppercase Arabic 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/url inputs 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 →