← Back to Index

Lesson 15

Logical Properties - Writing-Mode-Aware Layout

Traditional CSS uses physical directions: top, right, bottom, left. These are tied to the screen - they always mean the same thing regardless of language direction. Logical properties replace them with flow-relative directions that adapt to writing mode and text direction.

The mental model

Instead of thinking in physical directions (top/right/bottom/left), think in two axes:

English (LTR) block-start (top) inline-start (left) inline-end (right) block-end (bottom) Arabic (RTL) block-start (top) inline-start (right!) inline-end (left!) block-end (bottom) Block axis (stacking direction) Inline axis (text flow direction) Block stays the same. Inline flips with writing direction.

The principle: "Block" = the axis things stack on. "Inline" = the axis text flows on. "Start" and "end" replace "top/left" and "bottom/right." When the writing mode changes, logical properties automatically flip - physical properties don't.

The mapping - physical → logical

Physical (old) Logical (new) In English LTR
margin-top margin-block-start = top
margin-bottom margin-block-end = bottom
margin-left margin-inline-start = left (= right in RTL)
margin-right margin-inline-end = right (= left in RTL)
width inline-size = width
height block-size = height
top inset-block-start = top
left inset-inline-start = left (= right in RTL)
border-top border-block-start = top
text-align: left text-align: start = left (= right in RTL)

Shorthand properties

Logical properties also have two-value shorthands for setting both sides of an axis at once:

margin-block sets top + bottom. margin-inline sets left + right (in LTR).

margin-block: 1rem (top+bottom)
margin-inline: 2rem (left+right)
<!-- HTML -->
<div class="box">Content</div>

.box {
  margin-block: 1rem;     /* = margin-top + margin-bottom */
  margin-inline: 2rem;    /* = margin-left + margin-right (flips in RTL) */
  padding-block: 0.5rem;  /* = padding-top + padding-bottom */
  padding-inline: 1rem;   /* = padding-left + padding-right */
  border-inline: 1px solid gray;  /* left + right borders */
  inline-size: 300px;     /* = width */
  block-size: auto;       /* = height */
}

Live demo: LTR vs RTL

Same CSS, different direction - logical properties adapt automatically

direction: ltr (English)

border-inline-start is on the LEFT

direction: rtl (Arabic)

border-inline-start is on the RIGHT
.callout {
  border-inline-start: 4px solid blue;  /* left in LTR, right in RTL */
  padding-inline-start: 1rem;           /* same  -  adapts with direction */
  padding-block: 0.5rem;                /* top + bottom  -  always the same */
}

Why this matters even if you only build English sites:

  • Logical properties are shorter to write in many cases (margin-inline: auto vs margin-left: auto; margin-right: auto)
  • Future-proofs your CSS for i18n - if your app ever needs RTL support, it works out of the box
  • Aligns with how Flexbox and Grid already think (they use start/end, not left/right)
  • Modern linters (Stylelint) increasingly recommend logical properties as the default
Ad

The centering shorthand

margin-inline: auto centers horizontally - shorter than margin-left: auto + margin-right: auto

margin-inline: auto
<!-- HTML -->
<div class="centered">Content</div>

.centered {
  inline-size: 200px;     /* = width: 200px */
  margin-inline: auto;    /* = margin-left: auto; margin-right: auto */
  /* Centers horizontally  -  and flips correctly in RTL */
}

Inset - logical positioning

For positioned elements, logical equivalents of top/right/bottom/left:

inset-inline-start replaces "left" for positioned elements

inset-block-start + inset-inline-start
<!-- HTML -->
<div class="parent">
  <div class="positioned">Badge</div>
</div>

.positioned {
  position: absolute;
  inset-block-start: 0.5rem;    /* = top */
  inset-inline-start: 0.5rem;   /* = left (= right in RTL) */
}

/* Shorthand for all four: */
.overlay {
  position: absolute;
  inset: 0;  /* = top: 0; right: 0; bottom: 0; left: 0 */
}

border-radius - logical values

Even border-radius has logical equivalents:

/* Physical */
border-top-left-radius: 8px;

/* Logical */
border-start-start-radius: 8px;  /* block-start + inline-start corner */
border-start-end-radius: 8px;    /* block-start + inline-end corner */
border-end-start-radius: 8px;    /* block-end + inline-start corner */
border-end-end-radius: 8px;      /* block-end + inline-end corner */

In practice, border-radius: 8px (uniform) is used far more often than individual corners, so the logical versions of border-radius are less common.

When to use logical vs physical

Use logical when... Use physical when...
Spacing/alignment relates to text flow Position is tied to the viewport (e.g. "stick to the screen bottom")
Building reusable components Decorative effects not related to content direction
Your app may need RTL support (now or later) Explicitly positioning relative to the physical screen

Practical rule: Default to logical properties for everything content-related (margin, padding, border, sizing). Only use physical properties when you genuinely mean "the physical left edge of the screen" regardless of language direction.

Browser support

All logical properties: Chrome 87+, Safari 14.1+, Firefox 66+. Fully production-ready.

Retrieval check

Question 1

What does "block" mean in logical properties?

Block-level elements (divs, sections)
The axis blocks stack on (vertical in English)
The display: block value
A CSS containment boundary

Question 2

In an RTL language, what physical side does margin-inline-start apply to?

Left - inline-start is always left
Right - inline-start follows text direction
Top - RTL swaps block and inline
It's ignored in RTL

Question 3

What is the logical equivalent of width?

block-size
inline-size
logical-width
flow-size

Question 4

What does margin-inline: auto do?

Sets all four margins to auto
Sets left + right margins to auto (centers horizontally)
Sets top + bottom margins to auto
Only works inside a flex container

Question 5

What does inset: 0 do on a positioned element?

Removes all positioning
Sets top, right, bottom, left all to 0 (fills the positioned parent)
Resets the element to static positioning
Same as margin: 0