In CSS Flexbox, why does flex-direction: row lay items out right-to-left when the container's direction is rtl, and what does that mean for how you write layout rules?
answer
- axes come from the writing mode
- inline axis, not the horizontal one
- the start edge moves with direction
- the flex keywords localise themselves
- physical sides stay stubbornly physical
basics
~20 sFlexbox axes are writing-mode relative, not physical: row follows the container's inline axis, whose start edge is the right side under direction: rtl, and column follows the block axis. Flex-relative keywords flip automatically; hardcoded left and right in your CSS do not.
solid answer
~40 s`row` and `column` are not defined as horizontal and vertical — they are defined against the container's **inline** and **block** axes, which come from `writing-mode` and `direction`. Under `direction: rtl` the inline axis still runs horizontally but its start edge is on the right, so a `row` container packs items from the right. Under `writing-mode: vertical-rl` the inline axis is vertical, and `row` runs top to bottom. The practical consequence is that flex-relative keywords localise themselves for free — `flex-start`, `flex-end` and the placement order all flip — while anything physical does not: `margin-left: auto`, `left`/`right` offsets, `translateX`, directional icons and horizontal `box-shadow` offsets all stay put. So write flex-relative alignment and logical properties like `margin-inline-start`, and test a real `direction: rtl` subtree rather than treating `row-reverse` as an internationalisation strategy.
code
css · 11 lines.nav {
display: flex;
flex-direction: row; /* follows the inline axis */
justify-content: flex-start;
}
/* direction-aware push to the far end of the row */
.nav .profile { margin-inline-start: auto; }
/* the same container inside a right-to-left subtree */
.rtl-demo { direction: rtl; }go deeper
Know that row and column are defined against the writing direction rather than the screen, and that flex-start names an axis edge, not the left side.
Explain the mapping: row follows the inline axis, column the block axis, and direction or writing-mode decides where those point. Name a few physical properties that do not flip.
Demonstrate the diagnosis and the fix in real code: spot margin-left: auto and translateX as bidirectional hazards, reach for logical properties, and reject row-reverse as an internationalisation shortcut.
Own the standard. Decide that shared components are logical by default, that a right-to-left rendering is part of the review surface, and that physical values need a stated reason — retrofitting this across a mature codebase costs far more than mandating it early.
## The axes are logical, not physical The flexbox specification never defines `row` as "horizontal". It defines it against the container's **inline axis** — the direction text advances — and `column` against the **block axis** — the direction lines stack. Two CSS properties determine where those axes point: - `writing-mode` chooses whether the inline axis is horizontal or vertical (`horizontal-tb` versus `vertical-rl` / `vertical-lr`). - `direction` chooses which end of a horizontal inline axis is the start (`ltr` versus `rtl`). So the mapping is: | Container context | `row` runs | `column` runs | | --- | --- | --- | | `horizontal-tb`, `ltr` | left → right | top → bottom | | `horizontal-tb`, `rtl` | right → left | top → bottom | | `vertical-rl` | top → bottom | right → left | That is the whole answer to "why does my row reverse itself in Arabic or Hebrew": nothing reversed. The main axis is the inline axis, and the inline axis starts on the right in a right-to-left context. ## What flips for free Because the flex model is expressed in main-start/main-end and cross-start/cross-end terms, a large part of a layout localises itself with no extra code: - The order items are packed in follows the inline direction. - `flex-start` and `flex-end` follow the axis edges, so a rule that pins a control to `flex-end` pins it to the correct visual side in both directions. - The Box Alignment `start` and `end` keywords are likewise writing-mode relative. - Logical spacing properties — `margin-inline-start`, `padding-inline-end` and their siblings — flip with the inline axis. A subtle distinction worth knowing at this level: `flex-start` is *flex-relative* while `start` is *writing-mode relative*. In a `row-reverse` container the two diverge — `flex-start` follows the reversed main axis and lands on the right in a left-to-right context, while `start` stays with the inline-start edge on the left. ## What does not flip Everything physical, which is exactly where bidirectional bugs live: - `margin-left: auto` — the nav-bar trick for pushing an item to the far end. In RTL it pushes to the wrong side. `margin-inline-start: auto` is the direction-aware version. - Physical `padding`, `border-*` and `border-radius` corners. - `left` / `right` offsets on positioned elements, and physical `inset-*` values. - `transform: translateX()`, which is defined in physical coordinates. - `box-shadow` and `text-shadow` horizontal offsets, and physical `background-position` keywords. - Directional content: chevrons, back arrows, sort carets. CSS cannot know that a glyph implies direction; either flip it deliberately or ship a mirrored asset. ```css /* pushes to the wrong side in RTL */ .nav .profile { margin-left: auto; } /* follows the inline axis in both directions */ .nav .profile { margin-inline-start: auto; } ``` ## row-reverse is not an RTL strategy A recurring mistake is to "add RTL support" by switching containers to `row-reverse`. It is wrong on three counts. First, it localises nothing else — text alignment, list markers, physical margins and icons all stay in their left-to-right arrangement. Second, it decouples visual order from document order, so keyboard focus and reading order no longer follow the visuals, which is an accessibility regression rather than a fix. Third, it does not compose: a container that is already `row-reverse` for a genuine visual reason ends up double-reversed under a naive rule. The correct mechanism is to set the direction on the document or subtree and let the logical model do its job. ## Production judgment The senior-level version of this answer is about how you keep a codebase honest rather than how the spec is worded: - **Default to logical.** Flex-relative and inline/block-relative keywords in shared components; physical values only where the thing genuinely is physical, like a drop shadow's light source. - **Make it testable.** A page or story that renders the component tree inside a right-to-left subtree catches the physical leftovers immediately, and it is far cheaper than a translation pass discovering them. - **Watch the scroll and overflow edges.** When the inline axis flips, the edge that overflowing content spills toward flips with it, and so does the initial scroll position. Code that reads or sets scroll offsets and assumes zero means "leftmost" needs review. - **Nested writing modes are real.** A single component can sit in a differently-directed subtree from its parent, so rules that assume one direction for the whole document eventually break. Getting this right is cheap as a convention and expensive as a retrofit, which is precisely why it comes up in senior interviews: the question is not whether you can recite the mapping table but whether you would have written the layout in a way that never needed the retrofit.
- Is flex-direction: row-reverse a reasonable way to support right-to-left languages?No. It reverses one container's visual order and nothing else — text alignment, physical margins, icons and offsets stay left-to-right — and it decouples visual order from DOM order, so focus and reading order stop matching what users see. Set `direction` on the document or subtree and let the logical axes do the work.
- After switching a subtree to direction: rtl, which declarations still need attention?Every physical one: `margin-left`/`margin-right` and their padding equivalents, `left`/`right` offsets on positioned boxes, `transform: translateX()`, horizontal `box-shadow` offsets, physical `border-radius` corners and `background-position` keywords, plus any directional icon such as a back chevron. Flex-relative alignment and logical properties will already have flipped.
- How does writing-mode: vertical-rl change a row flex container?It makes the inline axis vertical, so `flex-direction: row` lays items out top to bottom and the cross axis becomes horizontal, running right to left. Nothing about the flex algorithm changes — only where the inline and block axes point — which is the cleanest demonstration that row never meant horizontal.
- What is the difference between the flex-start and start alignment keywords?`flex-start` is flex-relative: it follows the main or cross axis after `flex-direction`, including its reversed values. `start` is writing-mode relative: it follows the inline- or block-start edge regardless of reversal. They coincide in a plain `row` container and diverge in `row-reverse`, where `flex-start` is the right edge in a left-to-right context and `start` is the left.
saying these in an interview costs you the question
- Says flex-start always means the left edge
- Uses row-reverse as a right-to-left strategy
- Assumes margin-left: auto flips automatically in RTL
- Thinks column always means top-to-bottom regardless of writing mode
- Treats direction support as a translation-time concern only