Logical Properties and Writing Modes

Chapter 6 — Logical Properties and Writing Modes

Physical CSS properties — width, margin-left, border-top, top — assume horizontal text flowing left to right. They bake a specific writing system into the stylesheet. Logical properties replace physical directions with flow-relative references: inline (the axis text runs along) and block (the axis paragraphs stack along). A layout written with logical properties works correctly in Arabic (right to left), Japanese vertical text (top to bottom), and English — without a single overriding rule.

Baseline 2023. All logical properties and values covered in this chapter are supported in every evergreen browser. Writing modes have been supported since 2016. sideways-rl and sideways-lr are supported everywhere except Safari 17 and below — use vertical-rl with rotate as a fallback where needed.

1. The Inline and Block Axes

Every writing mode defines two axes. Inline is the direction text flows within a line. Block is the direction lines stack. The mapping of these axes to physical directions depends on the active writing mode and text direction.

writing-mode: horizontal-tb
direction: ltr
inline axis
block axis
inline-size = width
block-size = height
writing-mode: vertical-rl
Japanese / CJK
inline axis
block axis (RTL)
inline-size = height
block-size = width

The key insight: inline-size means "the size along the inline axis." In horizontal writing that is the width. In vertical writing that is the height. Write inline-size: 300px once and it works correctly in both.

2. Writing Modes

horizontal-tb
Hello World
こんにちは
vertical-rl
Hello World
こんにちは
vertical-lr
Hello World
こんにちは
sideways-rl
Hello World
/* writing-mode values */ writing-mode: horizontal-tb; /* Default. Text flows left→right (or right→left), lines stack top→bottom. */ /* Used by: Latin, Arabic, Hebrew, Greek, Cyrillic, Thai, Devanagari, etc. */ writing-mode: vertical-rl; /* Text flows top→bottom. Lines (columns) stack right→left. */ /* Used by: Traditional Chinese, Japanese, Korean (tategumi / vertical layout). */ /* The 'rl' means new columns appear to the LEFT of earlier columns. */ writing-mode: vertical-lr; /* Text flows top→bottom. Lines stack left→right. */ /* Used by: Mongolian script (traditional). */ writing-mode: sideways-rl; /* Entire glyphs are rotated 90° clockwise. Used for rotated Latin labels. */ /* Unlike vertical-rl, letters themselves are rotated — not individual glyphs. */ writing-mode: sideways-lr; /* Entire glyphs rotated 90° counter-clockwise. */ /* Axis mapping per writing mode */ /* inline-axis block-axis */ /* horizontal-tb: horizontal → vertical ↓ */ /* vertical-rl: vertical ↓ horizontal ← */ /* vertical-lr: vertical ↓ horizontal → */ /* sideways-rl: vertical ↓ horizontal ← */

text-orientation

/* text-orientation — applies only in vertical writing modes */ /* Controls how individual glyphs are oriented within a vertical line */ text-orientation: mixed; /* Default. CJK characters stand upright; Latin/digits are rotated 90° */ text-orientation: upright; /* All characters (including Latin) stand upright */ text-orientation: sideways; /* All characters are rotated 90° as a group (same as sideways-rl) */ /* Practical use: chapter numbers in a Japanese sidebar */ .chapter-number { writing-mode: vertical-rl; text-orientation: upright; /* digits stand upright in the vertical line */ }

3. Physical → Logical Property Mapping

Physical property Logical equivalent Notes
Sizing
width inline-size Size along the inline axis
height block-size Size along the block axis
min-width / max-width min-inline-size / max-inline-size
min-height / max-height min-block-size / max-block-size
Margin
margin-top margin-block-start
margin-bottom margin-block-end
margin-left margin-inline-start RTL: maps to right
margin-right margin-inline-end RTL: maps to left
margin: top right bottom left margin-block: start end; margin-inline: start end Two separate shorthands
Padding
padding-top padding-block-start
padding-bottom padding-block-end
padding-left padding-inline-start
padding-right padding-inline-end
Border
border-top border-block-start Shorthand: width style color
border-bottom border-block-end
border-left border-inline-start The accent border pattern
border-right border-inline-end
border-top-width border-block-start-width Longhand for width/style/color
Position / Inset
top inset-block-start
bottom inset-block-end
left inset-inline-start
right inset-inline-end
top:0; right:0; bottom:0; left:0 inset: 0 inset is the shorthand for all four
Border Radius
border-top-left-radius border-start-start-radius block-start + inline-start corner
border-top-right-radius border-start-end-radius block-start + inline-end corner
border-bottom-left-radius border-end-start-radius block-end + inline-start corner
border-bottom-right-radius border-end-end-radius block-end + inline-end corner
Text and Float
text-align: left text-align: start start/end are logical values for text-align
text-align: right text-align: end
float: left float: inline-start
float: right float: inline-end
resize: horizontal resize: inline
resize: vertical resize: block
overflow-x / overflow-y overflow-inline / overflow-block

4. Shorthands and Two-Value Syntax

/* Axis shorthands — apply to start and end together */ margin-block: 1rem; /* both block-start and block-end: 1rem */ margin-block: 1rem 2rem; /* block-start: 1rem; block-end: 2rem */ margin-inline: auto; /* centre horizontally (like margin: 0 auto but logical) */ margin-inline: 1rem 3rem; /* start: 1rem; end: 3rem */ padding-block: 0.5rem 1rem; padding-inline: 1.5rem; border-block: 1px solid var(--border); /* top and bottom borders */ border-block-start: 3px solid var(--accent); /* top border (or left in vertical) */ border-inline-start: 3px solid var(--accent); /* left border (or top in vertical) */ inset: 0; /* top:0; right:0; bottom:0; left:0 */ inset: 10px 20px; /* block:10px; inline:20px */ inset-block: 0; /* top:0; bottom:0 */ inset-inline: 0; /* left:0; right:0 — stretch horizontally */ /* Common pattern: stretch an element to fill its container */ .overlay { position: absolute; inset: 0; /* replaces top:0;right:0;bottom:0;left:0 */ } /* Centre with margin-inline: auto */ .container { max-inline-size: 1200px; margin-inline: auto; /* perfectly centred in any writing mode */ }

5. direction and RTL Layouts

/* direction controls text direction within horizontal-tb writing mode */ direction: ltr; /* default — inline-start = left, inline-end = right */ direction: rtl; /* Arabic/Hebrew — inline-start = right, inline-end = left */ /* In practice: set direction via HTML lang attribute and the browser user-agent */ /* Or use dir="rtl" on the HTML element or a section */ /* CSS direction should match the HTML dir attribute — don't set them independently */ /* The html element approach */ /* <html lang="ar" dir="rtl"> */ /* Logical properties respond automatically to dir="rtl" */ .card { border-inline-start: 3px solid var(--accent); padding-inline-start: 1rem; /* LTR: left border and left padding */ /* RTL: right border and right padding — no override needed */ } /* Physical properties require explicit RTL overrides */ .card--physical { border-left: 3px solid var(--accent); padding-left: 1rem; } [dir="rtl"] .card--physical { border-left: none; border-right: 3px solid var(--accent); padding-left: 0; padding-right:1rem; /* Four extra declarations for what logical did in zero */ }
LTR — dir="ltr"
📄
Article Title
border-inline-start creates a left accent border in LTR layout. padding-inline-start adds left padding.
RTL — dir="rtl"
📄
عنوان المقال
نفس خصائص CSS تماماً — ينعكس تلقائياً إلى اليمين في وضع RTL.

Both panels above use exactly the same CSS — border-inline-start and padding-inline-start. In LTR the accent appears on the left; in RTL it appears on the right. Zero extra CSS was written for the RTL version.

6. Logical Border Radius

/* Logical border-radius — format: border-{block-position}-{inline-position}-radius */ /* In horizontal-tb LTR: */ border-start-start-radius: 8px; /* top-left */ border-start-end-radius: 8px; /* top-right */ border-end-start-radius: 8px; /* bottom-left */ border-end-end-radius: 8px; /* bottom-right */ /* In horizontal-tb RTL: */ border-start-start-radius: 8px; /* top-right (inline-start = right in RTL) */ border-start-end-radius: 8px; /* top-left */ /* In vertical-rl: */ border-start-start-radius: 8px; /* top-right (block-start = top, inline-start = right in vertical-rl) */ /* Practical use: tab component that rounds the correct corners regardless of mode */ .tab { border-start-start-radius: 6px; border-start-end-radius: 6px; border-end-start-radius: 0; border-end-end-radius: 0; /* LTR: rounded top-left and top-right → correct horizontal tab shape */ /* vertical-rl: rounded top-right and bottom-right → correct vertical tab shape */ }

7. Flexbox, Grid, and Logical Properties

/* Flexbox already uses logical concepts internally */ /* flex-direction: row aligns items along the inline axis */ /* flex-direction: column aligns items along the block axis */ /* This means flex row items reverse order correctly when dir="rtl" */ /* However: explicit margins and padding still need logical properties */ .nav-item { margin-inline-end: 1rem; /* gap between items: right in LTR, left in RTL */ } /* CSS Grid: column and row always mean physical axes */ /* But gap, padding, and margin on grid children should use logical properties */ .grid-item { padding-inline: 1rem; padding-block: 0.75rem; } /* Writing mode changes in flexbox — the axis swaps */ .vertical-nav { writing-mode: vertical-rl; display: flex; /* flex-direction: row still means "along the inline axis" */ /* but in vertical-rl the inline axis is vertical */ /* so items stack vertically by default — no flex-direction change needed */ } /* Scroll bar position with logical overflow */ .scrollable { overflow-block: auto; /* vertical scrollbar in horizontal writing */ overflow-inline: hidden; /* no horizontal overflow */ } /* text-align: start / end — logical values */ .caption { text-align: start; /* left in LTR, right in RTL */ } .label { text-align: end; /* right in LTR, left in RTL */ }

8. Vertical Text Patterns

/* ── Vertical navigation labels (sideways) ─────────────────── */ .sidebar-label { writing-mode: sideways-rl; white-space: nowrap; text-align: center; } /* Fallback for Safari < 18 using rotate */ .sidebar-label { transform: rotate(90deg); white-space: nowrap; } @supports (writing-mode: sideways-rl) { .sidebar-label { transform: none; writing-mode: sideways-rl; } } /* ── Japanese magazine-style layout ────────────────────────── */ .article--tategumi { writing-mode: vertical-rl; block-size: 80vh; /* height of the reading column */ overflow-inline: auto; /* horizontal scroll (now the "block" axis) */ text-align: justify; hanging-punctuation: force-end; /* Japanese punctuation rules */ } /* ── Inline-size and block-size swap in vertical mode ────────── */ .vertical-card { writing-mode: vertical-rl; inline-size: 200px; /* this is the HEIGHT in vertical mode */ block-size: auto; /* this is the WIDTH — expands with content */ } /* ── Chapter headings with combined writing-mode + rotation ─── */ .chapter-heading { writing-mode: vertical-lr; text-orientation:upright; font-variant-numeric: tabular-nums; /* Digits stand upright in vertical text */ }

9. When to Use Logical vs Physical Properties

Default to logical. For any new CSS you write — sizing, spacing, borders, positioning — use logical properties. They work correctly across all writing modes and directions without any extra overrides. The cognitive overhead of mapping start/end to left/right is minimal and the payoff in internationalisation correctness is large.
/* When physical properties ARE correct */ /* 1. Truly physical UI that must not flip */ /* A progress bar that fills left-to-right regardless of document direction */ .progress-fill { width: 60%; /* must be physical — filling left-to-right is the metaphor */ left: 0; /* must anchor to the physical left edge */ } /* 2. Geometric/decorative elements that are direction-agnostic */ .avatar { width: 48px; /* a circle is a circle — physical or logical makes no difference */ height: 48px; } /* 3. Fixed-position elements anchored to physical screen edges */ .corner-badge { position: fixed; top: 1rem; /* physical screen corner — intentionally not logical */ right: 1rem; } /* 4. Images and media with inherent physical direction */ .hero-image { width: 100%; /* images are physical — width/height are correct */ height: auto; } /* The rule of thumb: */ /* If the property describes a relationship to the READING DIRECTION → logical */ /* If the property describes a geometric fact about PHYSICAL SPACE → physical */
Mixing logical and physical on the same property causes conflicts. margin-left and margin-inline-start both set the same computed value in LTR horizontal writing. If you set both, the one that appears later in the stylesheet wins (source order). Avoid mixing them — pick one model per component and be consistent.

Chapter Summary

ConceptKey point
Inline axisThe direction text flows within a line. Horizontal in horizontal-tb (LTR/RTL), vertical in vertical-rl/vertical-lr. inline-size, margin-inline, padding-inline, border-inline, inset-inline.
Block axisThe direction lines stack. Vertical in horizontal-tb, horizontal in vertical writing modes. block-size, margin-block, padding-block, border-block, inset-block.
start / endFlow-relative start and end of an axis. In LTR: inline-start = left, inline-end = right. In RTL: reversed. In vertical-rl: block-start = right.
writing-modehorizontal-tb (default), vertical-rl (Japanese), vertical-lr (Mongolian), sideways-rl/lr (rotated Latin labels). Changes which axis is inline and which is block.
text-orientationApplies in vertical writing modes. mixed (default: CJK upright, Latin rotated), upright (all upright), sideways (all rotated). Controls individual glyph orientation.
directionltr (default) or rtl. Controls inline-start/end mapping within horizontal-tb. Set via HTML dir attribute; match with CSS direction. Never set direction independently of dir.
insetShorthand for all four position offsets (top/right/bottom/left). inset: 0 replaces the four-property stretch pattern. inset-block and inset-inline for axis shorthands.
Logical border-radiusborder-start-start-radius etc. First word = block position (start/end), second word = inline position. Useful for tabs and dialog corners that must work in all writing modes.
text-align: start/endLogical values for text-align. start = left in LTR, right in RTL. Prefer over text-align: left for internationalised content.
When to use physicalTruly physical UI (progress bars, fixed corners, geometric shapes, images). Use physical when the meaning is about screen geometry, not reading direction.
Exercises
  1. Full logical property audit: Take a component you've built previously (card, nav, or form). Replace every physical property with its logical equivalent: width → inline-size, margin-left → margin-inline-start, border-top → border-block-start, top/left → inset-block-start/inset-inline-start, text-align: left → text-align: start. Wrap the component in a div dir="rtl" and confirm the layout mirrors correctly without any additional CSS.
  2. Writing mode sidebar label: Create a fixed-width sidebar. Add section labels that display as vertical rotated text using writing-mode: sideways-rl. Implement the @supports fallback using transform: rotate(90deg) for browsers without support. Confirm that inline-size on the label element controls its height (because the writing mode is vertical) and block-size controls its width.
  3. RTL-safe card component: Build a card with an icon on the start side, a title and body in the remaining space, and a "learn more" arrow link on the end side. Use only logical properties. Toggle between dir="ltr" and dir="rtl" on the card's container and verify: icon moves to the right, text aligns correctly, and the arrow points the right way (use inline-end margin on the arrow). Document in a comment which property would break if you had used its physical equivalent.
  4. Vertical tab navigation: Create a tab component where the tab list runs vertically along one side. Use writing-mode: vertical-rl on the tab labels so they read top-to-bottom. Apply border-start-start-radius and border-end-start-radius to the active tab to give it the correct rounding for a vertical tab shape. Then switch the whole component to dir="rtl" and observe which border-radius corners flip automatically.
  5. Container query + logical: Combine what you've learned in Chapters 2 and 6: create a card with container-type: inline-size. Write container queries that change the card's layout at a certain inline-size threshold. Inside the card, use only logical properties for all spacing and borders. Confirm the card works correctly when placed inside a writing-mode: vertical-rl container — the container query threshold should now be relative to the vertical axis since that is the inline axis in vertical writing.
Next: Chapter 7 — Advanced Color. The oklch() color space — why it produces perceptually uniform color scales, how to build a complete design token palette with it, the color-mix() function, wide-gamut colors with display-p3, light-dark(), relative color syntax, and @media (color-gamut) for progressive enhancement.