Engineering question: How do you build one set of components that renders correctly in both Arabic (right-to-left) and English (left-to-right) without maintaining two separate stylesheets — and without the layout breaking when content switches language?
The old approach was to ship a second rtl.css that mirrored every rule. It's a maintenance trap: every new component needs two implementations, and they drift apart. The modern approach is to write direction-agnostic CSS once, and let the browser mirror it.
Stop thinking in left/right; think start/end
Physical properties (margin-left, padding-right, left) are hard-coded to a direction. Logical properties are relative to the text flow, so they flip automatically when dir="rtl" is set.
| Physical (don't) | Logical (do) |
|---|---|
margin-left | margin-inline-start |
padding-right | padding-inline-end |
left: 0 | inset-inline-start: 0 |
text-align: left | text-align: start |
border-left | border-inline-start |
Write the component once with logical properties:
.card {
padding-inline-start: 1rem; /* "start" = left in LTR, right in RTL */
border-inline-start: 2px solid var(--accent);
text-align: start;
}Then the only thing the app sets per locale is the direction on the root element:
<html lang="ar" dir="rtl">
<!-- or lang="en" dir="ltr" -->
</html>The browser mirrors padding, border, and alignment for you. One stylesheet, both directions.
Flexbox and grid already understand direction
flex-direction: row follows the document direction, so a row of items reverses automatically in RTL — no override needed. The gap, order, and alignment all respect the inline axis. Where you previously reached for justify-content: flex-start, start-based logical values keep doing the right thing in both directions.
The things that must NOT mirror
Mirroring is correct for layout, but a few elements are direction-independent and should be pinned with physical properties on purpose:
- Numbers, dates, and phone numbers stay LTR even inside RTL text. Wrap them:
<span dir="ltr">+20 100 …</span>or useunicode-bidi: isolate. - Code blocks and URLs are always LTR.
- Brand logos and media controls (play/pause) usually shouldn't flip.
- Icons with directional meaning (a "back" chevron) should flip; decorative icons shouldn't. Decide per icon.
Don't forget the typography
RTL isn't only layout. Arabic needs a font with proper Arabic glyph coverage and tends to read better with slightly increased line-height, since Arabic letterforms are taller and more connected than Latin. Set the Arabic font stack on the lang="ar" scope so each language gets type tuned for it.
The result
By building on logical properties, flow-relative flexbox, and a single dir switch at the root — with deliberate exceptions for numbers, code, and logos — you get one component library that renders cleanly in both languages. New components are written once and work in both directions by default, which is the only way a bilingual product stays maintainable as it grows.