CSS logical properties
CSS logical properties map spacing, sizing, and positioning to the writing direction instead of fixed screen edges like left, right, top, or bottom.
Theory
TL;DR
- Think GPS directions that flip automatically: "start of text" replaces "left side of screen."
margin-leftstays screen-left in RTL;margin-inline-startjumps to screen-right.- One CSS rule covers LTR, RTL, and vertical text, no
[dir="rtl"]overrides needed. - Use logical properties if supporting RTL or vertical writing; physical is fine for simple LTR-only UIs.
- Browser support: Chrome, Firefox, Safari (2020+), Edge 79+. No IE.
Quick example
/* Physical - needs a duplicate rule for RTL */
.sidebar {
border-left: 2px solid #ccc;
}
[dir="rtl"] .sidebar {
border-left: none;
border-right: 2px solid #ccc; /* separate override required */
}
/* Logical - one rule handles both directions */
.sidebar {
border-inline-start: 2px solid #ccc; /* right side in RTL automatically */
}The logical version does not know or care about left or right. It knows "start of inline direction," which is left in LTR and right in RTL.
Key difference
Physical properties (margin-left, top, border-right) anchor to fixed viewport edges. They never move regardless of language. Logical properties (margin-inline-start, inset-block-start) resolve against the current writing mode: the inline axis follows text direction, the block axis follows line stacking. So margin-inline-start becomes margin-left in LTR and margin-right in RTL, same property, different paint instruction.
When to use
- RTL support (Arabic, Hebrew, Persian): logical properties everywhere.
- Vertical writing (CJK, manga-style layouts): logical +
writing-mode: vertical-rl. - Global product or design system: logical by default, physical only for SVG and decorative elements.
- LTR-only prototype you ship once: physical is fine, fewer concepts to juggle.
- Framework theme components (MUI, Chakra, Bootstrap RTL builds): logical for direction-agnostic spacing.
Comparison table
| Physical | Logical | LTR | RTL | vertical-rl |
|---|---|---|---|---|
margin-left | margin-inline-start | left | right | top |
margin-right | margin-inline-end | right | left | bottom |
margin-top | margin-block-start | top | top | right |
padding-bottom | padding-block-end | bottom | bottom | left |
width | inline-size | width | width | height |
height | block-size | height | height | width |
float: left | float: inline-start | left | right | top |
| Use when | LTR-only, legacy | RTL/global apps | CJK vertical UIs |
How browsers resolve logical properties
Browsers parse direction and writing-mode first during style computation, then resolve logical properties against those values per the CSS Writing Modes Level 3 spec. margin-inline-start becomes margin-left in LTR and margin-right in RTL. Blink and WebKit cache the resolved physical values in the layout tree, so there is no re-parse on dir changes when you already use logical properties. Changing dir does trigger a full layout recalculation though. Batch visual changes and use transform for animations if you are toggling direction at runtime.
One detail worth knowing: border-radius has no classic logical equivalents. The properties are border-start-start-radius, border-start-end-radius, border-end-start-radius, and border-end-end-radius. Support landed in Chrome, Firefox, and Safari around 2021.
Common mistakes
Mixing physical and logical on the same element:
/* Wrong - both apply, space adds up in LTR */
.box {
margin-left: 10px;
margin-inline-start: 20px; /* 30px total in LTR, not 20px */
}
/* Fix - pick one system */
.box {
margin-inline-start: 20px;
}Logical shorthand does not override physical. Both compute independently and stack. This surprises a lot of developers migrating a codebase to logical properties.
Expecting text-align: start to center content:
/* In RTL, start = right side. Not center. */
p { text-align: start; } /* Right-aligned in RTL */
/* For true center, use center - it has no logical equivalent */
p { text-align: center; }Using physical resize in vertical writing mode:
/* Does nothing useful in writing-mode: vertical-rl */
textarea { resize: horizontal; }
/* Fix */
textarea { resize: inline; } /* Resizes along the inline axis */Assuming position: sticky behaves the same in vertical mode:
In writing-mode: vertical-rl, position: sticky; inset-block-start: 0 sticks to the right edge because block-start equals right. Physical top: 0 ignores writing mode and sticks to the top of the viewport. These two produce completely different results. I found this one the hard way on a CJK dashboard where every sticky header was anchoring to the wrong edge in vertical layout. Test in Chrome DevTools RTL simulator before shipping.
Real-world usage
- Material-UI v6 uses
marginInlinein its theme system for RTL-aware spacing insxprops. - Tailwind CSS v3.4+ maps
ms-4tomargin-inline-startand handles RTL via thedirplugin. - Bootstrap 5.3 uses
pe-3(padding-inline-end) in RTL builds viadata-bs-dir="rtl". - Ionic Framework uses logical floats for Hebrew support in iOS/Android hybrid apps.
- Decision rule: logical if
dirchanges dynamically at runtime; physical is fine for static LTR SVGs or canvas drawings.
Add @supports (margin-inline-start: 0) to include a physical fallback for any legacy browser edge case.
Follow-up questions
Q: What happens to inline-size vs width in writing-mode: vertical-rl?
A: inline-size becomes height because the inline axis runs top-to-bottom. width stays screen-width. Use inline-size for manga or Japanese text layouts where the inline and block axes are swapped.
Q: How do logical properties interact with Flexbox align-items?
A: align-items: start aligns to the block-start edge, which is top in horizontal mode and right in vertical-rl. Flexbox axes follow the writing mode too, so the behavior shifts with the context.
Q: Name a property without a logical equivalent.
A: outline has no logical form. border-radius now has logical equivalents like border-start-start-radius, but they shipped years after the main logical properties and are still less commonly known.
Q: Does changing dir trigger reflow?
A: Yes, full layout recalculation. If you toggle LTR/RTL dynamically, batch DOM changes and use transform for animations to stay near 60fps.
Q: Senior question: in a shadow DOM with writing-mode: vertical-rl, how does position: sticky; inset-block-start: 0 behave vs physical top: 0?
A: The sticky container resolves to the right edge because block-start is right in vertical-rl. Physical top: 0 ignores writing mode and sticks to the top. Not interchangeable, they produce completely different layouts. This breaks most sticky navs in vertical writing contexts.
Examples
Physical vs logical in RTL
<!DOCTYPE html>
<html dir="rtl">
<head>
<style>
.card-physical {
margin-left: 20px; /* Stays screen-left even in RTL */
border-left: 4px solid blue; /* Wrong side for RTL layout */
}
.card-logical {
margin-inline-start: 20px; /* Moves to screen-right in RTL */
border-inline-start: 4px solid blue; /* Border on screen-right in RTL */
}
</style>
</head>
<body>
<div class="card-physical">Physical card</div>
<div class="card-logical">Logical card</div>
</body>
</html>In RTL: the physical card gets a left margin and left border, which is the wrong side for an RTL layout. The logical card flips both to the right automatically. Swap dir="rtl" to dir="ltr" and both look identical. That is the whole point.
RTL-aware React card
function Card({ title, children, dir = 'ltr' }) {
return (
<div dir={dir} style={{
marginInlineStart: '2rem', // RTL: becomes margin-right
paddingBlock: '1rem', // Same in all writing modes
inlineSize: '300px', // RTL: still 300px wide
borderInlineStart: '5px solid blue' // RTL: border appears on right
}}>
<h3>{title}</h3>
{children}
</div>
);
}
// Usage
<Card dir="rtl" title="مرحبا">Content goes here</Card>
// Result: blue border on right, 2rem right margin, 300px wideMaterial-UI v6 uses this pattern in its theme system. The marginInlineStart in the sx prop adapts to the dir attribute on the nearest parent without any extra RTL configuration.
Vertical writing mode with logical sizing
.manga-panel {
writing-mode: vertical-rl; /* inline axis = top-bottom, block = right-left */
inline-size: 200px; /* maps to height (inline runs vertically) */
block-size: 100%; /* maps to width */
margin-block-end: 20px; /* maps to margin-left in vertical-rl */
}<div class="manga-panel">縦書きテキスト</div>A common mistake here: float: inline-start in writing-mode: vertical-rl maps to float: top, which legacy float does not support. Use flexbox or grid for item flow in vertical writing contexts.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.