
CSS Logical Properties: Writing Layouts for Every Language
Your product is launching in Arabic next month. You add dir="rtl" to the html element, reload, and the layout is a mess. Icons sit on the wrong side of their labels, the sidebar is still pinned to the left, list bullets have indentation on one side and text hugging the other, and every carefully tuned margin-left is now pushing things in exactly the wrong direction.
The usual fix is a second stylesheet full of overrides that flip every left to right and every right to left. It works, but it doubles your maintenance and it's easy to miss things.
CSS logical properties remove the problem at the source. Instead of describing space in physical directions like left, right, top, and bottom, you describe it relative to the flow of text: where a line starts and ends, and where a block of text starts and ends. When the writing direction changes, the layout follows automatically. In this guide, I'll explain the model, map the physical properties to their logical equivalents, and walk through converting a real component.
Physical vs. Logical: The Mental Model
Physical properties are tied to the screen. margin-left is always the left side of the box, no matter what language is inside it.
Logical properties are tied to the writing mode and direction of the content. They use two axes:
- The inline axis is the direction text runs within a line. In English, that's left to right. In Arabic and Hebrew, it's right to left. In vertical Japanese, it's top to bottom.
- The block axis is the direction in which lines and blocks stack. In English, that's top to bottom. In vertical Japanese, blocks stack from right to left.
Each axis has a start and an end:
inline-start: where a line begins. Left in English, right in Arabic.inline-end: where a line finishes. Right in English, left in Arabic.block-start: where the first line sits. Top in horizontal writing.block-end: where the last line sits. Bottom in horizontal writing.
Once you think in terms of start and end, you stop caring about which physical side that happens to be.
Mapping Physical Properties to Logical Ones
Here are the conversions you'll use most, assuming a horizontal, left-to-right context for the physical column.
Margin, Padding, and Border
| Physical | Logical |
|---|---|
margin-top | margin-block-start |
margin-bottom | margin-block-end |
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-left / padding-right | padding-inline-start / padding-inline-end |
border-left | border-inline-start |
border-top-left-radius | border-start-start-radius |
There are also handy two-value shorthands per axis:
.card {
margin-block: 1rem 2rem; /* block-start, block-end */
padding-inline: 1.5rem; /* both inline sides */
border-block-end: 1px solid #e2e8f0;
}
margin-block, margin-inline, padding-block, padding-inline, border-block, and border-inline all work the same way: one value sets both sides, two values set start then end.
Sizing
| Physical | Logical |
|---|---|
width | inline-size |
height | block-size |
min-width / max-width | min-inline-size / max-inline-size |
min-height / max-height | min-block-size / max-block-size |
In horizontal writing, inline-size and width are the same thing. The distinction matters in vertical writing modes, where inline-size becomes the height.
Positioning
| Physical | Logical |
|---|---|
top | inset-block-start |
bottom | inset-block-end |
left | inset-inline-start |
right | inset-inline-end |
The inset shorthand is physical (top, right, bottom, left), but inset-block and inset-inline are logical shorthands.
Text and Floats
.pull-quote {
float: inline-end;
text-align: start;
clear: inline-start;
}
text-align: start and end have been around for a long time. The inline-start and inline-end values for float and clear are newer but now supported across all major browsers.
Converting a Real Component
Let's take a common media object: an avatar next to a comment, with a small badge in the corner and a colored border marking unread comments.
<article class="comment comment--unread">
<img
class="comment__avatar"
src="/img/amira.jpg"
alt=""
width="48"
height="48"
/>
<div class="comment__body">
<h3 class="comment__author">Amira Haddad</h3>
<p class="comment__text">
This fixed the layout for our Arabic storefront. Thank you!
</p>
</div>
<span class="comment__badge">New</span>
</article>
The Physical Version
.comment {
position: relative;
display: flex;
gap: 1rem;
padding: 1rem 1.25rem 1rem 1rem;
border-left: 4px solid transparent;
border-radius: 0 0.75rem 0.75rem 0;
}
.comment--unread {
border-left-color: #4f46e5;
}
.comment__avatar {
margin-top: 0.25rem;
border-radius: 50%;
}
.comment__author {
margin: 0 0 0.25rem 0;
text-align: left;
}
.comment__badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
In a right-to-left page, the flex layout flips correctly on its own, because flexbox is already logical. But the border stays on the left, the rounded corners are on the wrong side, the author name is forced left, and the badge still sits in the top right, overlapping the avatar.
The Logical Version
.comment {
position: relative;
display: flex;
gap: 1rem;
padding-block: 1rem;
padding-inline: 1rem 1.25rem;
border-inline-start: 4px solid transparent;
border-start-end-radius: 0.75rem;
border-end-end-radius: 0.75rem;
}
.comment--unread {
border-inline-start-color: #4f46e5;
}
.comment__avatar {
margin-block-start: 0.25rem;
border-radius: 50%;
}
.comment__author {
margin-block: 0 0.25rem;
margin-inline: 0;
text-align: start;
}
.comment__badge {
position: absolute;
inset-block-start: 0.75rem;
inset-inline-end: 0.75rem;
}
Now the same CSS works in both directions. In English, the unread border is on the left and the badge in the top right. In Arabic, the border moves to the right and the badge to the top left, without a single override.
The corner radius properties deserve a note. They're named border-[block]-[inline]-radius, so border-start-end-radius is the corner where block-start meets inline-end. In horizontal left-to-right text, that's the top-right corner.
Setting Direction Correctly
Logical properties respond to the element's direction and writing mode, so those need to be set properly.
Use the dir Attribute, Not CSS
For right-to-left languages, set dir in the HTML:
<html lang="ar" dir="rtl"></html>
You can technically use the CSS direction property, but the HTML attribute is the correct choice. It's semantic, it's respected even if CSS fails to load, and it lets the browser handle bidirectional text properly. Set lang too, since it affects fonts, hyphenation, and screen reader pronunciation.
For mixed content, like a user's Hebrew name inside an English interface, dir="auto" asks the browser to detect the direction from the content:
<p dir="auto">שלום עולם</p>
Writing Modes
For vertical text, as in traditional Japanese, Chinese, or Korean layouts, use writing-mode:
.vertical-heading {
writing-mode: vertical-rl;
padding-block-start: 1rem;
inline-size: 20rem;
}
In vertical-rl, the inline axis runs top to bottom and blocks stack right to left. That means padding-block-start is now on the right, and inline-size controls the height. The logical properties just work, whereas physical ones would all need rethinking.
Vertical writing isn't only for East Asian languages. It's also popular for decorative side labels and table headers:
.table-header--vertical {
writing-mode: vertical-lr;
rotate: 180deg;
white-space: nowrap;
}
Handling Things That Shouldn't Flip
Not everything should mirror in right-to-left layouts. Knowing the exceptions is part of doing this well.
- Icons with direction meaning like back arrows, next arrows, and "reply" icons should flip.
- Icons without direction meaning like a search magnifier, a checkmark, or a clock should not.
- Media controls like play buttons and progress bars generally stay left to right, because they represent time rather than reading direction.
- Logos, phone numbers, and code snippets stay as they are.
For directional icons, mirror them using the :dir() pseudo-class:
.icon--directional:dir(rtl) {
scale: -1 1;
}
:dir() matches based on the element's actual direction, including inherited dir attributes, which makes it more reliable than an attribute selector like [dir="rtl"] .icon. It's supported in all current major browsers.
For code blocks, force left-to-right explicitly:
pre,
code {
direction: ltr;
text-align: left;
unicode-bidi: isolate;
}
Here, the physical text-align: left is intentional. Code is always left to right, regardless of the surrounding language.
Flexbox and Grid Are Already Logical
Some good news: many layout properties have been logical from the start.
- In flexbox,
justify-content: flex-startmeans the inline-start side. A row of flex items reverses automatically in RTL. - In grid, column line 1 is on the inline-start side.
grid-column: 1 / 3places an item at the start, whichever side that is. gap,align-items,justify-items, andplace-contentare all logical.
That's why the flex layout in the comment example flipped correctly even before we converted anything. Most of the conversion work is in spacing, borders, positioning, and text alignment.
The main trap is row-reverse and explicit order values used to fake a visual order. These interact with direction and can end up double-reversed in RTL. Prefer to write the source order you want and let direction handle it.
Migrating an Existing Codebase
You don't have to convert everything at once.
- Start with new code. Make logical properties the default for everything you write from now on.
- Lint for physical properties. Stylelint has plugins that flag
margin-left,padding-right, and similar, and can auto-fix many of them. - Convert components that are reused most. Buttons, cards, form fields, and navigation give the most return.
- Keep physical properties where they're intentional, like the code block example, and leave a comment saying why.
A quick way to find what's left to convert:
grep -rnE "(margin|padding|border)-(left|right)|(^|[^-])(left|right):|text-align:[[:space:]]*(left|right)" src/styles
Browser Support
Logical properties for margin, padding, border, sizing, and inset are supported in every current major browser and have been for years. float: inline-start and inline-end were among the last pieces to arrive in Chromium-based browsers, but they're now broadly supported as well. For almost all projects, logical properties can be used without fallbacks.
If you do need to support a legacy browser, declare the physical property first, then the logical one:
.note {
margin-left: 1rem;
margin-inline-start: 1rem;
}
Just be aware that in RTL contexts on those legacy browsers, the physical fallback will be on the wrong side, so this is only a partial fix.
Common Pitfalls
- Mixing physical and logical properties for the same side. If you set
margin-leftandmargin-inline-starton the same element, whichever comes later in the cascade wins in LTR, but in RTL they target different sides and both apply. Pick one approach per element. - Assuming
insetis logical. The four-valueinsetshorthand maps to top, right, bottom, left. Useinset-blockandinset-inlinefor logical control. - Forgetting the
langattribute. Direction alone doesn't set the right font fallbacks or hyphenation rules. - Mirroring everything. Flipping every icon and image with a global rule causes more problems than it solves. Be deliberate.
Conclusion
Logical properties replace "left, right, top, bottom" with "start and end, inline and block". That one change of vocabulary means your layouts adapt automatically to right-to-left languages and vertical writing modes, with no override stylesheet to maintain.
Set dir and lang correctly in your HTML, use margin-inline, padding-block, inset-inline-end, inline-size, and their siblings in your components, and reserve physical properties for the rare cases where direction genuinely shouldn't change. Even if you never ship a right-to-left version, writing logically makes your CSS more honest about its intent, and makes the day someone asks for Arabic or Hebrew support a lot less stressful.


