Write margin-left: 16px to put space between an icon and its label, and it works perfectly in English. Translate the page into Arabic, set dir="rtl" on <html>, and the text now flows right to left, the icon moves to the right of the label, and the 16px of space is still on the icon’s left, on the wrong side, between the icon and the edge of the card. The usual fix is a second stylesheet full of [dir="rtl"] .icon { margin-left: 0; margin-right: 16px; } overrides, which doubles every direction-dependent rule and drifts out of sync.
Logical properties remove the need for those overrides. Instead of naming a physical side of the screen, you name a side relative to how the text flows, and the browser maps it.
Inline and block, start and end
Two axes replace “horizontal” and “vertical”:
- The inline axis is the direction text runs within a line. In English it’s left to right. In Arabic and Hebrew it’s right to left. In vertical Japanese it’s top to bottom.
- The block axis is the direction lines and paragraphs stack. In both English and Arabic it’s top to bottom. In vertical Japanese (
writing-mode: vertical-rl) it’s right to left.
Each axis has a start and an end. For English, inline-start is left, inline-end is right, block-start is top and block-end is bottom. For Arabic, inline-start is right. The browser works this out from the element’s writing-mode and direction, which it gets from dir attributes in the HTML.
The common mappings:
physical logical
margin-left margin-inline-start
margin-right margin-inline-end
padding-top padding-block-start
padding-left + right padding-inline
width inline-size
height block-size
min-height min-block-size
top / bottom (positioned) inset-block-start / inset-block-end
left / right (positioned) inset-inline-start / inset-inline-end
border-top-left-radius border-start-start-radius
text-align: left text-align: start
float: left float: inline-startThe two-value shorthands are handy: margin-inline: 16px 8px sets start then end, and padding-block: 12px sets top and bottom in horizontal writing.
Converting a card, step by step
Here’s a notification card written with physical properties:
.notice {
border-left: 4px solid #1d4ed8;
padding: 12px 16px 12px 20px;
text-align: left;
}
.notice .icon {
float: left;
margin-right: 12px;
}
.notice .close {
position: absolute;
top: 8px;
right: 8px;
}In an RTL page, every one of these is wrong: the accent border is on the left (it should be on the side where the text starts, now the right), the extra padding is on the left, text is forced to align left, the icon floats to the left with its gap on the wrong side, and the close button sits top-right, which in RTL is the start of the line (RTL users expect close buttons top-left, at the end).
Convert each declaration by asking “does this follow the text, or the screen?”:
.notice {
border-inline-start: 4px solid #1d4ed8;
padding-block: 12px;
padding-inline: 20px 16px;
text-align: start;
}
.notice .icon {
float: inline-start;
margin-inline-end: 12px;
}
.notice .close {
position: absolute;
inset-block-start: 8px;
inset-inline-end: 8px;
}With dir="ltr", this renders exactly like the original: accent bar on the left, 20px padding on the left and 16px on the right, icon at the left with 12px after it, close button top-right. With dir="rtl", everything mirrors: accent bar on the right, 20px padding on the right, icon at the right with 12px to its left, close button top-left. One rule set, no overrides.
padding: 12px 16px 12px 20px had to be split into padding-block and padding-inline because the four-value padding shorthand is always physical. There’s no logical keyword for the shorthand yet, so asymmetric shorthands are where most conversion work is.
Direction belongs in the HTML
Set direction with the dir attribute, not the CSS direction property:
<html lang="ar" dir="rtl">The attribute is part of the content: it survives when CSS fails to load, it’s what browsers and assistive technology use for bidirectional text handling, and it scopes neatly to fragments (a quoted English product name inside an Arabic paragraph can be wrapped in <span dir="ltr">). For user-generated content where you don’t know the direction, dir="auto" lets the browser pick from the first strong character.
You can style direction-specific exceptions with the :dir() pseudo-class, :dir(rtl) .arrow { transform: scaleX(-1); }, which matches by the computed direction, including inherited and auto cases, unlike the attribute selector [dir="rtl"], which only matches the element that has the attribute.
Flexbox and grid are already logical
justify-content: flex-start, flex-direction: row, grid-column: 1 and gap all follow the inline direction. A display: flex row in RTL lays out its first child on the right, with no changes. This is why converting a modern layout is usually less work than it looks: the layout engine is already direction-aware, and only the margins, paddings, borders, positions and text alignment on individual elements need converting.
The order property and flex-direction: row-reverse are the exceptions to watch; they reverse the visual order relative to the DOM, and in RTL that reversal is mirrored too, so a component that used row-reverse to put something “on the right” will put it on the left in Arabic.
When to keep physical properties
Not everything should flip. Ask whether the meaning comes from the language or from the physical world:
- Shadows and lighting. A
box-shadowoffset downward because light comes from above stays physical. There are no logical shadow offsets anyway. - Media controls. Play arrows and progress bars for video are usually not mirrored in RTL interfaces, because they represent time on a physical timeline that many RTL users still expect left to right.
- Icons with direction meaning. A “next” chevron should point toward the inline end, so it does flip (often via
:dir(rtl)withscaleX(-1)). A checkmark or a clock icon shouldn’t. - Vertical writing gotchas.
inline-sizebecomes physical height underwriting-mode: vertical-rl. If a component setsinline-size: 300pxexpecting a width, it will be 300px tall in vertical text. That’s correct if the component should follow the writing mode, and surprising if it was meant to be a fixed-width sidebar.
Interview practice
You’re adding Hebrew support to a dashboard. The sidebar uses position: fixed; left: 0; width: 260px;, the main area uses margin-left: 260px, breadcrumbs use › characters between items with margin: 0 8px, and table numbers are text-align: right. What do you change?
Answer
Add dir="rtl" and lang="he" to the <html> element for Hebrew pages, generated by the server or framework from the locale. Then convert the layout: the sidebar becomes inset-inline-start: 0; inline-size: 260px; and the main area margin-inline-start: 260px;, so the sidebar sits on the right in Hebrew and the main content is offset from it. (If the sidebar and main area were a grid or flex container, they’d mirror without changes, which might be the better refactor.) The breadcrumb margins are symmetric, so they’re fine, but the › separator points the wrong way in RTL: swap it for a direction-neutral separator such as /, or flip it with :dir(rtl) and a transform, or render it as an icon that’s mirrored in RTL. For the numbers, the intent is “align numbers so their digits line up”, and numbers in Hebrew text still read left to right, so text-align: end (which becomes left in RTL) matches the convention of aligning numeric columns at the inline end. Use font-variant-numeric: tabular-nums so digits have equal widths. Test with real Hebrew strings, not mirrored English, because mixed-direction text (product codes, emails) exposes most of the remaining issues.
Keep going
- HTML and CSS topic hub
- HTML and CSS multiple-choice questions
- CSS positioning and containing blocks
- Flexbox vs Grid: choosing a layout
- MDN: CSS logical properties and values