---
name: rtl-bilingual
description: Right-to-left and Arabic/English bilingual interface rules — logical CSS properties, direction handling, icon mirroring, Arabic typography metrics, numerals, and mixed-direction strings. Use this whenever writing or reviewing layout CSS, positioning anything, choosing an icon, setting type, formatting numbers or dates, or building any component that will render in Arabic. Also use when a physical direction word (left, right, ltr) appears anywhere in a style, token name, prop or asset name.
---

# RTL and Arabic/English

These products ship in Arabic and English from the same components. That is a
constraint on how everything is written, not a localisation pass at the end. A
component built with `margin-left` is wrong in half the deployments and nobody
notices until an Arabic-reading user files a bug about a broken layout.

The rule underneath everything here: **describe position relative to reading order,
not relative to the screen.**

## Logical properties only

Never write a physical direction in a style, a token name, a prop, or an asset name.

| Never | Always |
|---|---|
| `margin-left`, `margin-right` | `margin-inline-start`, `margin-inline-end` |
| `padding-left/right` | `padding-inline-start/end` |
| `left`, `right` (positioning) | `inset-inline-start`, `inset-inline-end` |
| `text-align: left` | `text-align: start` |
| `border-left` | `border-inline-start` |
| `border-radius: 4px 0 0 4px` | `border-start-start-radius` etc., or a logical shorthand |
| `float: left` | `float: inline-start` |
| `translateX(-8px)` | logical offset, or flip under `[dir="rtl"]` |

In Tailwind: `ms-4` / `me-4`, not `ml-4` / `mr-4`; `ps-` / `pe-`, not `pl-` / `pr-`;
`start-0` / `end-0`, not `left-0` / `right-0`; `text-start`, not `text-left`.

Run the linter before you claim a component is RTL-safe:

```bash
node scripts/lint-physical-properties.mjs src/
```

It fails on physical properties, physical Tailwind utilities, physical direction
words in token and prop names, and `transform: translateX` with a hardcoded sign.
The last one is the sneaky one — it looks direction-neutral and is not.

**The two legitimate exceptions**, both of which must carry a comment saying which:
1. Something anchored to a physical device edge regardless of language — a debug
   overlay, a hardware-aligned affordance.
2. Content whose direction is fixed by its own nature — a code block, a terminal, a
   waveform, a chart's time axis.

## Direction

- `dir` is set on `<html>` and inherited. Do not set `dir` per component.
- Set `lang` alongside it. Font stack, hyphenation and screen reader voice all key off
  `lang`, not `dir`, and getting one without the other produces English text read in
  an Arabic voice.
- A mixed-direction string needs `dir="auto"` on its container — an Arabic sentence
  containing an English product name, or an English sentence containing an Arabic
  name. Without it, punctuation lands on the wrong end of the string. This is the
  single most visible bilingual defect and the cheapest to fix.
- Never mirror by transform. `transform: scaleX(-1)` on a container reverses text,
  shadows and images along with the layout.

## Icon mirroring

`reference/icon-mirroring.md` has the full list. The principle: **mirror icons that
depict direction of reading or progress; never mirror icons that depict a real-world
object or a universal convention.**

Mirror: back/forward chevrons, breadcrumb separators, undo/redo, indent/outdent,
list bullets, "reply", progress arrows, the send arrow, tree disclosure chevrons.

Never mirror: media transport (play, fast-forward, rewind — these follow the tape,
not the text), clocks and any clockwise motion, checkmarks, magnifying glass, most
logos, the mute speaker, and anything containing latin text or numerals.

The one that starts arguments: **the send arrow mirrors, the play triangle does not.**
Send points in the direction the message travels, which is reading direction. Play
points along a timeline, which is not.

## Arabic typography

Arabic is not English in a different font. Setting them at the same metrics produces
Arabic that looks cramped and undersized next to its English counterpart.

- **Size**: Arabic needs roughly 5–10% more optical size than Latin at the same
  perceived weight. Tune per family and check against real Arabic strings, not
  Lorem-style filler.
- **Line height**: roughly 8–12% more than Latin, and the ratio is not constant
  across the type scale — display sizes need proportionally less extra than body.
  This is why type tokens are composites: a caller cannot mix a Latin line height
  with an Arabic size if the two never separate.
- **No synthetic bold or italic.** Arabic has no italic; slanting it is wrong, not
  merely unusual. Use a real weight from a family that has one, or express emphasis
  another way.
- **No letter-spacing.** Arabic is a connected script; tracking breaks the joins.
  Set `letter-spacing: 0` under `[lang="ar"]` explicitly, because a Latin-tuned
  tracking token will otherwise inherit straight through.
- **Do not uppercase.** Arabic has no case. A `text-transform: uppercase` on a shared
  label component is a no-op in Arabic and silently produces inconsistent emphasis
  between the two languages — pick a different emphasis mechanism.
- **Ascender and descender space**: Arabic descends further. Tight fixed-height
  containers that fit English clip Arabic. Prefer intrinsic height with logical
  padding.

## Numerals, dates, currency

- Decide once, per product, whether Arabic locales use Western (0123) or Eastern
  Arabic-Indic (٠١٢٣) numerals, and put the decision in the locale config rather
  than in components. Both are correct in different markets; inconsistency is not.
- Numbers themselves run left-to-right inside RTL text. The browser handles this via
  the bidi algorithm — do not "help" it by reversing digits.
- Format dates, times, numbers and currency with `Intl`, never by string
  concatenation. Concatenation is where currency symbols end up on the wrong side.
- Percentages, phone numbers and version strings are all bidi hazards. Wrap them in
  an element with an explicit `dir` where they appear inside prose.

## Checking a component

1. Run the linter.
2. Render in `ar` with real Arabic content, not transliterated placeholder — 
   placeholder text hides the descender and joining problems entirely.
3. Check the boundaries: a right-aligned English name inside Arabic prose, a number
   at the start of a sentence, an English URL in an Arabic paragraph.
4. Tab through it. Focus order follows DOM order, which is reading order — if focus
   jumps around in Arabic, the layout is being done with positioning rather than
   flow.
5. Check any component with a chevron, an arrow, or a slide-in animation. Motion has
   handedness too: a drawer that enters from the inline-end in English enters from
   the inline-end in Arabic, which is the opposite side of the screen.
