Lesson 48 • Advanced Track
CSS Logical Properties
By the end of this lesson you'll be able to write one set of spacing, border, and alignment rules that lays out correctly in English, Arabic, and vertical Japanese — no right-to-left overrides, no duplicated stylesheets.
What You'll Learn
margin, padding, or selectors feel new, review CSS Basics & Selectors first.💡 Think of It Like This
Physical properties are like telling someone "the light switch is on the left wall." That only works if they walk in facing the way you imagined. Turn them around — or stand them in a different room — and "left" now points at the wrong wall.
Logical properties are like saying "the switch is by the door you came in through." That instruction stays correct no matter which way the person faces. When you write margin-inline-start instead of margin-left, you're describing the side relative to how the text flows, so the browser points it at the right wall automatically — in English, in Arabic, or in vertical Japanese.
1. The Inline Axis and the Block Axis
Every logical property is named after one of two axes. The inline axis runs along the direction text flows — across the line. The block axis is perpendicular to it — the way new lines and paragraphs stack. Each axis has a start and an end, so every physical edge gets a flow-relative name.
In English (left-to-right), the inline axis is horizontal: inline-start = left, inline-end = right. The block axis is vertical: block-start = top, block-end = bottom. Change the language to Arabic and only the inline side flips; switch to vertical Japanese and the two axes rotate together. You write the names once and the browser resolves the physical edge at render time.
Run the worked example. The blue accent bar is on border-inline-start. The second card uses the identical CSS but sits in a direction: rtl container — and the bar moves to the other side by itself.
Worked example: one rule, both directions
border-inline-start flips automatically from LTR to RTL
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Logical axes</title>
<style>
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.card {
background:#1e293b;
padding-block: 16px; /* top + bottom (block axis) => 16px each */
padding-inline: 20px; /* start + end (inline axis) => 20px each */
margin-block-end: 16px; /* space BELOW the card in LTR */
border-radius:
...2. Margin, Padding & Border Shorthands
Just like margin: 10px 20px is shorthand for the four physical sides, logical properties have per-axis shorthands. margin-inline sets both inline edges at once; margin-block sets both block edges. Give two values to set start and end separately: padding-block: 12px 20px is 12px on top, 20px on the bottom (in LTR).
| Shorthand | Expands to | Example |
|---|---|---|
margin-inline | inline-start + inline-end | margin-inline: 20px; |
margin-block | block-start + block-end | margin-block: 8px 16px; |
padding-inline | padding start + end (inline) | padding-inline: 24px; |
border-inline | border on both inline edges | border-inline: 2px solid; |
Worked example: per-axis shorthands
margin-inline, padding-block and a two-value shorthand
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Logical shorthands</title>
<style>
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.panel {
background:#1e293b; border-radius:10px;
margin-inline: auto; /* centre horizontally (auto on both inline sides) */
max-inline-size: 420px; /* like max-width, but flow-relative */
padding-inline: 24px; /* 24px left AND right in LTR */
...3. Logical Sizing & Positioning
The axes also rename width and height. inline-size is the measurement along the inline axis (width in normal text) and block-size is along the block axis (height). In a vertical writing mode these swap, which is exactly what you want — a "300px wide" column should stay 300px across the reading direction, not 300px across the screen.
For positioned elements, inset is the logical shorthand for the offset edges. inset: 0 pins all four sides (handy for a full-cover overlay), inset-inline targets the two inline edges, and inset-inline-start replaces left in LTR / right in RTL.
Worked example: inline-size and inset
A badge positioned with inset-block-start + inset-inline-end
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sizing & inset</title>
<style>
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.tile {
position: relative; /* makes inset-* on children resolve to this box */
inline-size: 100%; /* = width: 100% */
max-inline-size: 360px; /* = max-width: 360px */
block-size: 140px; /* = height: 140px */
background:#1e293
...4. Text Alignment & Writing Modes
The smallest, highest-value swap is alignment. Replace text-align: left with text-align: start and your paragraphs align to the reading direction — left in English, right in Arabic — with no override. Pair it with direction (rtl/ltr) or writing-mode (e.g. vertical-rl for vertical CJK text) and the whole layout follows.
The example shows the same CSS rendered three ways: horizontal LTR, horizontal RTL, and vertical. Notice the accent bar and the text alignment land on the correct edge in every panel without a single direction-specific rule.
Worked example: LTR vs RTL vs vertical
text-align: start and logical borders across three writing modes
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Writing modes</title>
<style>
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.row { display:grid; grid-template-columns:1fr 1fr 1fr; gap:14px; }
@media (max-width:640px){ .row { grid-template-columns:1fr; } }
/* ONE set of rules, reused by all three panels below. */
.panel {
background:#1e293b; border-radius:10px; min-block-size:160px;
paddin
...🎯 Your Turn #1 — Convert physical to logical
This card is styled with physical properties, so its accent bar stays on the left even in the RTL container below it. Swap the three marked lines to logical properties so the bar flips automatically. Fill in the blanks marked ___, then run it.
Your Turn #1: make the accent bar RTL-aware
Replace margin-left / padding / border-left with logical equivalents
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Turn 1</title>
<style>
/* 🎯 YOUR TURN — fill in the blanks marked ___ */
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.card {
background:#1e293b; border-radius:10px; max-inline-size:360px; margin-block-end:16px;
/* 1) Give equal left + right padding using ONE inline shorthand. */
___ /* 👉 add: padding-inline
...🎯 Your Turn #2 — Align text and pin a badge logically
The heading should align to the reading direction, and the badge should sit in the inline-end / block-start corner so it lands top-right in English but top-left in RTL. Fill in the two blanks, then check the expected result in the comments.
Your Turn #2: text-align: start + logical inset
Use start alignment and inset-inline-end on a corner badge
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Turn 2</title>
<style>
/* 🎯 YOUR TURN — fill in the blanks marked ___ */
body { font-family: system-ui, sans-serif; background:#0f172a; color:#e5e7eb; padding:24px; }
.rtl { direction: rtl; }
.tile {
position: relative; inline-size:100%; max-inline-size:340px; min-block-size:120px;
background:#1e293b; border-radius:12px; padding-inline:16px; padding-block:14px;
margin-block-end:1
...🧩 Mini-Challenge — A fully bidirectional card
Support is faded now — only an outline is given. Build a notification card that lays out correctly in both LTR and RTL using only logical properties. Use the worked examples in sections 1–4 as your reference if you get stuck.
Mini-Challenge: logical-only notification card
Spacing, accent bar, alignment and a corner dismiss — all logical
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mini-Challenge</title>
<style>
/* 🧩 MINI-CHALLENGE: a notification card with ZERO physical sides
1. Style .note with: padding-inline + padding-block, a max-inline-size,
margin-inline: auto to centre it, and border-inline-start for an accent bar
2. Set text-align: start on .note so the text follows the reading direction
3. Position the .x dismiss button absolutely in the inline-END / bl
...⚠️ Common Errors (and the fix)
- Mixing physical and logical on the same box. Using
margin-leftnext topadding-inlinemeans the layout half-flips in RTL — the worst of both worlds. Fix: pick logical for a component and convert every edge, so the whole thing flips together. - Leaving
text-align: leftin. Logical spacing flips but the text stays jammed against the physical left edge in Arabic. Fix: usetext-align: start(andend) for body text. - Using
lefton a positioned element. An absolutely-positioned badge withleft: 12pxstays on the wrong side in RTL. Fix: useinset-inline-start/inset-inline-end. - Forgetting the corner radius is physical too.
border-top-left-radiuswon't follow the flow. Fix: use the logical corner —border-start-start-radius(block-start, inline-start). - Assuming logical means "horizontal-only RTL". It also covers vertical writing modes, where physical width/height break entirely. Fix: use
inline-size/block-sizeinstead ofwidth/heighton flowing content.
📋 Quick Reference — Physical → Logical
| Physical | Logical | Axis (LTR meaning) |
|---|---|---|
margin-left | margin-inline-start | Inline start (left) |
margin-left + margin-right | margin-inline | Both inline edges |
padding-top + padding-bottom | padding-block | Both block edges |
border-left | border-inline-start | Inline start (left) |
width / height | inline-size / block-size | Across / down the flow |
max-width | max-inline-size | Inline axis cap |
top / right / bottom / left | inset | All four offsets |
left (positioned) | inset-inline-start | Inline start offset |
text-align: left | text-align: start | Reading-direction start |
border-top-left-radius | border-start-start-radius | Block-start, inline-start corner |
❓ Frequently Asked Questions
What is the difference between the inline axis and the block axis?
The inline axis runs along the direction text flows — left-to-right in English, right-to-left in Arabic, top-to-bottom in vertical Japanese. The block axis is perpendicular to it, the direction new lines and blocks stack. So in normal English the inline axis is horizontal and the block axis is vertical, but in a vertical writing mode those swap. Logical properties name sides by these axes (inline-start, block-end) instead of by fixed screen edges (left, bottom), which is exactly why they keep working when the writing direction changes.
Will margin-inline-start always mean margin-left?
No, and that is the whole point. In a left-to-right language inline-start resolves to left, but in a right-to-left language it resolves to right, and in a vertical-rl writing mode it resolves to top. The browser computes the physical side at render time from the element's direction and writing-mode. If you actually need 'always the left edge no matter the language' — which is rare — then a physical property like margin-left is the correct tool.
Do I need to support RTL languages to bother with logical properties?
It still pays off even if you ship in English only. Logical properties make a component drop-in reusable, so if your product is ever translated you change nothing. They are also the only way to handle vertical writing modes. And they read more clearly: padding-inline and padding-block describe intent (the two axes) rather than four separate physical edges. Modern frameworks like Tailwind now default to them (ms-4, ps-4), so you will meet them regardless.
Is text-align: start the same as text-align: left?
Only in a left-to-right context. text-align: start aligns text to the start of the inline axis, which is the left in LTR but the right in RTL. text-align: left always pins to the physical left edge, which looks wrong in Arabic or Hebrew. Use start and end for body text so paragraphs align naturally in every language; reserve left and right for the rare case where the physical edge truly matters regardless of language.
How does inset relate to top, right, bottom, and left?
inset is the logical shorthand for positioning offsets on a positioned element. inset: 0 sets all four offsets to 0 (a common full-cover pattern), and it respects writing direction. You can also target one axis with inset-inline (the two inline edges) and inset-block (the two block edges), or a single edge with inset-inline-start. So inset-inline-start replaces left in LTR and right in RTL, the same flip you get with margins and borders.
Are logical properties safe to use in production today?
Yes. margin-inline, padding-block, inset, inline-size and the rest have been supported across Chrome, Firefox, Safari and Edge for years and are safe in production. The newer logical corner radii (border-start-start-radius) are also broadly supported. Only very old browsers lack them, and in those the layout degrades to default spacing rather than breaking. For new code, defaulting to logical properties is the current best practice.
🎉 Lesson Complete
You can now write CSS that lays out correctly in any writing direction. The essentials:
- ✅ inline = the reading axis, block = perpendicular; each has a
startandend - ✅ Use
margin-inline,padding-block,border-inline-startinstead of left/right/top/bottom - ✅ Size with
inline-size/block-sizeand position withinset/inset-inline-start - ✅ Align with
text-align: start, notleft - ✅ One stylesheet then works in LTR, RTL, and vertical modes — the win for internationalisation
- ✅ Migrate incrementally: convert a component's every edge, don't mix physical and logical
Next up: Responsive Without Frameworks, where you'll build fluid layouts using modern CSS instead of a UI library.
Sign up for free to track which lessons you've completed and get learning reminders.