Skip to content
Locale Lab

Chapter 13·Scripts and layout·~8 min

RTL layouts: what flips and what doesn't

The bidi chapter covered text: the Unicode bidirectional algorithm reorders characters on its own. Nothing reorders the layout. Margins, offsets, and icons mirror only when written as CSS logical properties.

RTLCSS logical propertiesdir=rtl

The problem

Every margin-left, left: 12px, and text-align: left in a stylesheet bakes a left-to-right reading order into the layout. Set dir="rtl" for an Arabic or Hebrew build and the text right-aligns, but the back button stays glued to the left, chevrons point into their rows, and the progress bar fills from the wrong end. An English-only review never catches the half-mirrored result: physical and logical CSS render identically in LTR.

How it works

The two halves of RTL

The bidi chapter covered the first half: the Unicode bidirectional algorithm reads character properties and reorders text runs on its own. An Arabic sentence lays out right-to-left before anyone writes a line of CSS. The natural next assumption is that the rest of the page follows. It does not. Margins, offsets, icon positions, and progress bars know nothing about reading direction, because margin-left means the compass-left side of the box in every locale.

The switch for the second half is dir="rtl" on the html element. On its own it does real work: blocks right-align, table columns run right to left, flex and grid reverse their inline axis. But it can only reach CSS that speaks in flow terms, which is what logical properties are. margin-inline-start names the side where a line of text begins, so it lands on the left under LTR and on the right under RTL. A physical property names a fixed side and stays there. Written logically, a layout mirrors on that one attribute. Written physically, it needs a second, overriding stylesheet. That is why build tools like RTLCSS exist to machine-rewrite left into right for teams that cannot migrate.

CSS shipped compass sides first and flow sides decades later because early CSS assumed one writing direction, and a retrofit cost a full mirrored vocabulary. Logical properties give every physical property a flow-relative twin (left → inset-inline-start, border-top-left-radius → border-start-start-radius) so one stylesheet can serve both directions. In LTR the twins behave identically, so the bug hides until the first Arabic or Hebrew build sets the attribute.

<html dir="rtl" lang="ar">

.row {
  padding-inline-start: 44px;  /* was padding-left  */
  text-align: start;           /* was left          */
}
.row .chevron {
  inset-inline-end: 8px;       /* was right         */
}
One attribute flips the document, and logical properties let the flip reach the layout.

Icons are a separate audit

Logical properties move boxes, never glyphs. A back arrow repositioned to the right edge still points left, so directional icons need their own rule (the standard one is [dir="rtl"] .icon { transform: scaleX(-1) }). The harder question is which icons get it. Material Design's guidance draws the line at meaning: mirror what depicts navigation or the passage of time (back and forward arrows, list chevrons, progress bars), and do not mirror what depicts a physical object or circular time (keyboards, headsets, and clocks, which turn clockwise everywhere).

Two verdicts are easy to get wrong. The search magnifier looks directional but is not: its handle sits bottom-right because it depicts a right hand holding the glass, and right-handedness holds in RTL countries too. Media playback stays LTR entirely: play, fast-forward, and the scrubber follow the direction of the media rather than the direction of time. A video scrubber and a download progress bar showing the same 60% therefore mirror differently. One verdict is even per-script: icons containing a question mark mirror for Arabic and Farsi (Arabic script has its own mirrored question mark, ؟) but not for Hebrew, which keeps the Latin one.

The demo

Try these, in order

Each step reproduces one specific failure in the demo below.

  1. 1
    Leave the Language toggle on English and compare the two frames. They render pixel-identical: physical and logical CSS agree completely under LTR, which is how the bug class survives every English-only review.
  2. 2
    Click Arabic and read the Physical CSS column. The text right-aligned on its own: that half is the bidi algorithm. But the back control is still on the left, chevrons point into the rows, and numbered markers sit on each break. The list under the frame, “Where the physical column breaks”, names all four.
  3. 3
    Click marker 3, on the progress bar. The fill anchors at left: 0, a physical property, so the bar still fills from the left. An RTL progress bar shows 0% on the right and fills right to left.
  4. 4
    Now read the Logical CSS column, still in Arabic. Same markup, fully mirrored: header, rows, bar, and button all flipped because inline-start now resolves to the right. The only RTL-specific rule in that column is the transform that flips the directional icons.
  5. 5
    In the “What flips and what doesn't” table, compare the Progress bars row with the Media playback & scrubber row. Both are a bar filling left to right, but one flips and one does not. A progress bar depicts time, and a scrubber depicts the media. The verdict comes from what the element means rather than what it looks like.
  6. 6
    Read the last row of “The property map”. text-align: start is a one-word change from left, and it decides whether the column mirrors.

Language

Both frames get dir="ltr" and lang="en". The columns differ only in which CSS properties they use.

Physical CSS

left / right · padding-left / padding-right · margin-left: auto · text-align: left

Settings
Profile
Notifications
Language
Progress
60%
Continue

Logical CSS

inset-inline-start / end · padding-inline-start / end · margin-inline-start: auto · text-align: start

Settings
Profile
Notifications
Language
Progress
60%
Continue

What flips and what doesn't

Mirror anything that depicts reading direction: arrows, chevrons, linear progress. Clocks, media playback, and physical objects like the search magnifier stay in place.

ElementVerdictWhy
Back / forward arrowsFlipsForward points left in RTL, back points right. · Material bidirectionality
Chevrons in list rowsFlipschevron_left and chevron_right are on Material's list of icons to mirror. A row chevron points at the row's inline end. · Material icons guide
Progress barsFlipsAn RTL progress bar shows 0% on the right and 100% on the left, and fills right to left. · Material bidirectionality
Text alignmentFlipsdir="rtl" right-aligns blocks on its own. text-align: start / end keeps overrides direction-aware. · W3C i18n
Search magnifierDoesn't flipThe handle sits bottom-right because it depicts a right hand holding the glass, and right-handedness holds in RTL countries too. · Material bidirectionality
Media playback & scrubberDoesn't flipPlayback buttons and the media progress indicator follow the direction of the media, not the direction of time. · Material bidirectionality
Clocks, refresh, historyDoesn't flipClocks still turn clockwise: RTL does not mirror the circular direction of time. · Material bidirectionality
Camera & other non-directional iconsDoesn't flipIcons that do not communicate direction stay unmirrored. · Material bidirectionality
Question mark (help icon)Depends on scriptMirrored for Arabic and Farsi (؟ is a distinct character), but not for Hebrew, which keeps the Latin question mark. · Material icons guide

Verdicts follow Material Design's bidirectionality guidance, its icons-in-RTL guide, and the W3C i18n guidance on document direction.

The property map

Every physical property has a logical equivalent. The two behave identically in LTR; under dir="rtl" the logical one resolves to the opposite side. Names per MDN and CSS Logical Properties and Values Level 1.

Physical (LTR-only)Logical (direction-aware)
margin-leftmargin-inline-start
padding-rightpadding-inline-end
leftinset-inline-start
border-top-left-radiusborder-start-start-radius
text-align: lefttext-align: start

The short version

The bidi algorithm mirrors text for free. Nothing mirrors the layout. Write CSS in logical properties (inline-start / inline-end) so dir="rtl" on the html element flips the whole UI. Then audit icons separately, because CSS moves boxes, not glyphs.

What to do about it

  • Write layout in logical properties by default: margin-inline-start, padding-inline-end, inset-inline-start, text-align: start. In LTR they cost nothing; under dir="rtl" they mirror the layout without a second stylesheet.
  • Set dir="rtl" and lang on the html element, not per component; direction is a document-level decision that tables, forms, and alignment all inherit.
  • Audit icons separately: CSS moves boxes, not glyphs. Mirror back/forward arrows and list chevrons; leave playback controls, clocks, and the search magnifier alone (Material Design's bidirectionality guidance has the full list).
  • Add a lint rule that flags physical properties in new CSS, and run an RTL pass before translation lands; the breakage is visible with placeholder text.

Where this comes up

Who it concerns

EngineeringDesignQA

Moments

  • ·RTL launch readiness review
  • ·Component library audit
  • ·Design handoff for an Arabic or Hebrew market

Field note

“Set dir="rtl" and ship” is the classic first Arabic build. The text right-aligns, but every margin-left, left offset, and left-pointing chevron stays put: the half-mirrored UI this demo's physical column reproduces. Ahmad Shadeed's RTL Styling 101, written from examples on production Arabic UIs including LinkedIn's and Twitter's, exists because the failure is that common. It walks the physical-to-logical fixes property by property.

RTL Styling 101: Ahmad Shadeed ↗

Terms in this chapter

Where to read more

Related chapters