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
こんにちは
/* 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
| Concept | Key point |
| Inline axis | The 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 axis | The direction lines stack. Vertical in horizontal-tb, horizontal in vertical writing modes. block-size, margin-block, padding-block, border-block, inset-block. |
| start / end | Flow-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-mode | horizontal-tb (default), vertical-rl (Japanese), vertical-lr (Mongolian), sideways-rl/lr (rotated Latin labels). Changes which axis is inline and which is block. |
| text-orientation | Applies in vertical writing modes. mixed (default: CJK upright, Latin rotated), upright (all upright), sideways (all rotated). Controls individual glyph orientation. |
| direction | ltr (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. |
| inset | Shorthand 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-radius | border-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/end | Logical values for text-align. start = left in LTR, right in RTL. Prefer over text-align: left for internationalised content. |
| When to use physical | Truly physical UI (progress bars, fixed corners, geometric shapes, images). Use physical when the meaning is about screen geometry, not reading direction. |
Exercises
- 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.
- 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.
- 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.
- 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.
- 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.