Ch. 5 · HTML & CSS

CSS Logical Properties for Direction-Aware Layout

Swap left and right for inline-start and inline-end so one stylesheet serves English, Arabic and vertical text, with a card converted.

~6 min readadvancedupdated Oct 7, 2026

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-start
Text

The 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;
}
CSS

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;
}
CSS

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">
HTML

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-shadow offset 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) with scaleX(-1)). A checkmark or a clock icon shouldn’t.
  • Vertical writing gotchas. inline-size becomes physical height under writing-mode: vertical-rl. If a component sets inline-size: 300px expecting 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

More in HTML & CSS

read ✓HTML & CSS · hard

HTML Data Tables and Header Relationships

How th, scope, caption and headers/id tell a screen reader which headers describe a cell, with a two-level pricing table worked through.

~7 min readread →
read ✓HTML & CSS · easy

HTML Buttons Versus Links for Actions

Links go somewhere and buttons do something. What each element gives you for free, and what breaks when a div or an a tag fakes the other.

~6 min readread →
esc