Skip to main content
Back to the blog

RTL is not a mirror

Right-to-left support is a layout architecture decision you make on day one, not a translation task handed to whoever is free in the last sprint.

The request usually arrives as a line item: “add Arabic”. It gets filed next to the copy work and estimated at a week. Then someone flips dir="rtl" on the html element, the layout comes apart, and the estimate quietly triples.

The mistake is the noun. Arabic is a translation task. Right-to-left is a layout architecture: you either encode direction in how the layout is expressed, or you spend the rest of the project patching it.

Stop writing left and right

Most RTL bugs are one bug repeated: a stylesheet full of physical properties. margin-left, padding-right, left: 0, border-left. Each is a claim about the screen; none is a claim about the text. Flip direction and every one is wrong — and the usual remedy is a second stylesheet under [dir='rtl'] that overrides all of them, which starts drifting from the first immediately.

Logical properties remove the problem instead of mirroring it:

/* Physical: correct in one direction, wrong in the other */
.card {
  margin-left: 1rem;
  border-left: 1px solid;
}

/* Logical: correct in both, no override needed */
.card {
  margin-inline-start: 1rem;
  border-inline-start: 1px solid;
}

inline-start means “the edge the reader starts from”, and the browser already knows which edge that is because dir told it. This site uses border-inline-start and padding-inline-start for the spine running down every page, and inset-inline for the rule under a link. There is no RTL stylesheet to keep in sync. Adopting this in a new codebase costs nothing — the same number of characters. Retrofitting it into forty components is a rewrite you cannot ship incrementally, because half-converted spacing looks worse than either extreme.

Transforms do not know about direction

Logical properties get you most of the way, then you hit the wall: transform has no logical variant. translateX(1rem) moves right in both directions, and transform-origin: left center stays left in Arabic. Every directional animation — a drawer entering from the reading edge, an underline growing from where the eye starts — silently plays backwards.

The fix is not to special-case the animation. It is to make direction a value the animation reads:

:root {
  --stroke-origin: left center;
}
[dir='rtl'] {
  --stroke-origin: right center;
}

.rule-link::after {
  inset-inline: 0;
  transform: scaleX(0);
  transform-origin: var(--stroke-origin);
  transition: transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.rule-link:hover::after {
  transform: scaleX(1);
}

One override, once, at the root. Every directional animation added later inherits the fix.

Letter-spacing breaks Arabic

Arabic is a connected script: letters join, and the joins carry information about position in the word. letter-spacing inserts a gap between glyphs and pulls those joins apart. The word does not look airy, it looks broken — the way t h i s does in Latin, except a native reader has to stop and reassemble it.

That kills the small-label pattern every design system reaches for: uppercase, tracked out, monospace. In Arabic those are three separate impossibilities — no uppercase, tracking severs the script, and most mono faces carry no Arabic coverage at all. So the label gets re-thought rather than translated:

.marginalia {
  font-family: var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

html[lang='ar'] .marginalia {
  font-family: var(--font-body-ar);
  font-size: 0.78rem; /* size carries the hierarchy instead */
  letter-spacing: normal;
  text-transform: none;
}

Bidirectional runs are where it gets sharp

Arabic prose is full of Latin: version numbers, product names, phone numbers, years. Each is a left-to-right run inside a right-to-left paragraph, and the Unicode bidirectional algorithm decides where the boundaries fall. Neutral characters — spaces, punctuation, +, parentheses — sit on those boundaries and resolve by context. That is how a phone number ends up with its + on the wrong end, or Q1 2026 renders as 2026 Q1.

The algorithm is not wrong; it was handed an ambiguous string and resolved it by rule. Remove the ambiguity by isolating the run:

.numeric {
  font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
}

isolate makes the element a single neutral object seen from outside, and resolves its inside independently. In markup <bdi> does the same, and is what you want around any string you did not write yourself — a person’s name, a company name, anything out of a database. Isolate every run whose direction you cannot guarantee at build time.

The typefaces have to agree

Arabic and Latin faces drawn independently do not share metrics. Set them at the same font-size and the Arabic usually reads smaller, sits differently on the baseline, and wants more line-height, because the script’s vertical demands are genuinely different.

Two moves cover most of it. Pick a superfamily drawn across both scripts, so body text is metrically compatible by construction. Then correct the residual optical difference once:

html[lang='ar'] {
  font-size: 104%;
}

Remember what else that scales: everything in rem. Right for type, wrong for a page gutter that should be identical in both locales — which is why this site’s container padding is declared in px.

Display type is a design decision rather than a technical one. One face that “does Arabic too” gives you a legible page with no voice. A face from each tradition, matched on quality rather than shape, gives two pages that read as the same publication without either imitating the other.

What it costs

None of this is difficult, and all of it is expensive late. Logical properties, a direction variable for transforms, isolated bidi runs and a matched pairing amount to an afternoon of decisions at the start of a project — or a fortnight of regressions at the end of one.

If the layout treated direction as a first-class input, adding Arabic costs close to nothing. If it did not, you are not adding a language. You are rebuilding the layout, this time with a deadline.

Have something to build?

Email us in Arabic or English, whichever is easier, or call us directly.

Contact details

Direct contact