{"id":801,"date":"2026-04-03T07:15:00","date_gmt":"2026-04-03T07:15:00","guid":{"rendered":"https:\/\/www.cssportal.com\/blog\/?p=801"},"modified":"2026-04-03T07:15:00","modified_gmt":"2026-04-03T07:15:00","slug":"css-logical-properties","status":"publish","type":"post","link":"https:\/\/www.cssportal.com\/blog\/css-logical-properties\/","title":{"rendered":"CSS Logical Properties"},"content":{"rendered":"<style>\n:root { --ink: #0f0e0d; --paper: #f5f0e8; --accent: #c84b2f; --accent2: #2563eb; --code-bg: #1a1917; --code-text: #e8e4da; --rule: #0f0e0d22; --serif: 'Instrument Serif', Georgia, serif; --mono: 'DM Mono', monospace; --sans: 'Mona Sans', sans-serif; }\narticle { margin: 0 auto; }\n.logical-blog section { margin-top: 4.5rem; }\n.logical-blog h2 { font-family: var(--serif); font-size: clamp(1.6rem, 3.5vw, 2.2rem); font-weight: 400; line-height: 1.2; margin-bottom: 1.1rem; position: relative; padding-left: 1.2rem; }\n.logical-blog h2::before { content: ''; position: absolute; left: 0; top: 0.2em; bottom: 0.2em; width: 3px; background: var(--accent); }\n.logical-blog h3 { font-family: var(--mono); font-size: 1rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent2); margin: 2.2rem 0 0.8rem; }\n.logical-blog p { margin-bottom: 1.25rem; }\n.logical-blog ul, .logical-blog ol { margin: 0.75rem 0 1.25rem 1.5rem; line-height: 2; }\n.logical-blog li { margin-bottom: 0.2rem; }\n.logical-blog strong { font-weight: 600; }\n.logical-blog em { font-style: italic; }\n.logical-blog code { font-family: var(--mono); font-size: 0.87em; background: #0f0e0d12; padding: 0.15em 0.45em; border-radius: 3px; color: var(--accent); }\n.logical-blog pre { background: var(--code-bg); color: var(--code-text); font-family: var(--mono); font-size: 0.84rem; line-height: 1.75; padding: 1.5rem; border-radius: 6px; overflow-x: auto; margin: 1.5rem 0; border-left: 3px solid var(--accent); }\n.logical-blog pre .comment { color: #666; }\n.logical-blog pre .prop { color: #79b8ff; }\n.logical-blog pre .val { color: #f97583; }\n.logical-blog pre .sel { color: #85e89d; }\n.logical-blog pre .at { color: #ffab70; }\n.logical-blog .callout { background: #fff; border: 1.5px solid var(--ink); border-left: 5px solid var(--accent2); padding: 1.25rem 1.5rem; margin: 2rem 0; border-radius: 0 6px 6px 0; }\n.logical-blog .callout.warn { border-left-color: #d97706; }\n.logical-blog .callout.tip { border-left-color: #16a34a; }\n.logical-blog .callout strong { font-family: var(--mono); font-size: 0.75rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--accent2); display: block; margin-bottom: 0.4rem; }\n.logical-blog .callout.warn strong { color: #d97706; }\n.logical-blog .callout.tip strong { color: #16a34a; }\n.logical-blog .mapping-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; margin: 1.5rem 0; }\n.logical-blog v.mapping-table th { font-family: var(--mono); font-size: 0.85rem; letter-spacing: 0.12em; text-transform: uppercase; background: var(--ink); color: var(--paper); padding: 0.65rem 1rem; text-align: left; }\n.logical-blog .mapping-table td { padding: 0.6rem 1rem; border-bottom: 1px solid var(--rule); vertical-align: top; }\n.logical-blog .mapping-table tr:nth-child(even) td { background: #f5f0e8; }\n.logical-blog .mapping-table td code { background: transparent; padding: 0; }\n.logical-blog .prop-physical { color: #888; }\n.logical-blog .prop-logical { color: var(--accent); font-weight: 500; }\n.logical-blog .demo-wrap { border: 2px solid var(--ink); border-radius: 8px; overflow: hidden; margin: 2rem 0; }\n.logical-blog .demo-header { background: var(--ink); color: var(--paper); padding: 0.6rem 1rem; display: flex; justify-content: space-between; align-items: center; font-family: var(--mono); font-size: 0.85rem; letter-spacing: 0.1em; text-transform: uppercase; }\n.logical-blog .demo-header span { color: #aaa; }\n.logical-blog .demo-body { padding: 1.5rem; background: #fff; }\n.logical-blog .prop-viz { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; align-items: center; }\n.logical-blog .prop-viz-box { position: relative; width: 100%; aspect-ratio: 1.4; border: 2px solid #ddd; border-radius: 4px; display: flex; align-items: center; justify-content: center; overflow: hidden; transition: all 0.3s; }\n.logical-blog .prop-viz-box .center-text { font-family: var(--mono); font-size: 0.75rem; z-index: 1; text-align: center; line-height: 1.8; }\n.logical-blog .axis-label { position: absolute; font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; background: #fff; padding: 0 4px; }\n.logical-blog .axis-label.block-start { top: 5px; left: 50%; transform: translateX(-50%); color: var(--accent); }\n.logical-blog .axis-label.block-end { bottom: 5px; left: 50%; transform: translateX(-50%); color: var(--accent); }\n.logical-blog .axis-label.inline-start { left: 5px; top: 50%; transform: translateY(-50%) rotate(-90deg); color: var(--accent2); }\n.logical-blog .axis-label.inline-end { right: 5px; top: 50%; transform: translateY(-50%) rotate(90deg); color: var(--accent2); }\n.logical-blog .prop-list { display: flex; flex-direction: column; gap: 0.4rem; }\n.logical-blog .prop-item { display: flex; justify-content: space-between; align-items: center; padding: 0.4rem 0.75rem; border-radius: 4px; font-family: var(--mono); font-size: 0.75rem; cursor: pointer; border: 1.5px solid transparent; transition: all 0.15s; }\n.logical-blog .prop-item:hover { border-color: #ccc; }\n.logical-blog .prop-item.block.active { border-color: var(--accent); background: #fff5f5; }\n.logical-blog .prop-item.inline.active { border-color: var(--accent2); background: #eff6ff; }\n.logical-blog .prop-item .physical { color: #999; font-size: 0.7rem; }\n.logical-blog .dir-demo { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }\n.logical-blog .dir-btn-group { grid-column: 1 \/ -1; display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.75rem; align-items: center; }\n.logical-blog .dir-btn { font-family: var(--mono); font-size: 0.75rem; padding: 0.35rem 0.8rem; border: 1.5px solid var(--ink); background: transparent; cursor: pointer; border-radius: 3px; transition: all 0.15s; }\n.logical-blog .dir-btn:hover, .dir-btn.active { background: var(--ink); color: var(--paper); }\n.logical-blog .dir-box { padding: 1rem; border: 1.5px dashed #ccc; border-radius: 4px; font-size: 0.88rem; }\n.logical-blog .dir-label { font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; color: #999; margin-bottom: 0.5rem; }\n.logical-blog #demo-physical .inner-box { margin-top: 10px; margin-left: 24px; padding: 8px 20px; background: #fee2e2; border-radius: 4px; border-left: 3px solid #f87171; transition: all 0.3s; font-size: 0.85rem; }\n.logical-blog #demo-logical .inner-box { margin-block-start: 10px; margin-inline-start: 24px; padding-block: 8px; padding-inline: 20px; background: #dbeafe; border-radius: 4px; border-inline-start: 3px solid #60a5fa; transition: all 0.3s; font-size: 0.85rem; }\n.logical-blog .writing-demo { display: grid; grid-template-columns: repeat(auto-fit, minmax(175px, 1fr)); gap: 1rem; }\n.logical-blog .writing-card { border: 1.5px solid var(--rule); border-radius: 6px; overflow: hidden; cursor: pointer; transition: all 0.2s; }\n.logical-blog .writing-card:hover { border-color: var(--ink); transform: translateY(-2px); }\n.logical-blog .writing-card.active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }\n.logical-blog .writing-card-header { background: var(--ink); color: var(--paper); padding: 0.4rem 0.75rem; font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; }\n.logical-blog .writing-card-body { padding: 1rem; min-height: 100px; display: flex; align-items: flex-start; }\n.logical-blog .writing-sample { background: #f0f0f0; border-radius: 4px; padding: 0.5rem 0.75rem; font-size: 0.85rem; line-height: 1.7; }\n.logical-blog .playground { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; align-items: start; }\n.logical-blog .playground-controls { display: flex; flex-direction: column; gap: 0.65rem; }\n.logical-blog .control-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }\n.logical-blog .control-label { font-family: var(--mono); font-size: 0.75rem; color: #555; flex-shrink: 0; min-width: 160px; }\n.logical-blog .control-row input[type=range] { flex: 1; accent-color: var(--accent2); }\n.logical-blog .control-val { font-family: var(--mono); font-size: 0.75rem; color: var(--accent2); min-width: 38px; text-align: right; }\n.logical-blog .playground-preview { background: #f8f8f8; border-radius: 6px; border: 1.5px solid #e0e0e0; padding: 1rem; min-height: 180px; display: flex; align-items: flex-start; }\n.logical-blog .playground-box { background: #dbeafe; border-inline-start: 3px solid #2563eb; border-radius: 4px; font-size: 0.85rem; transition: all 0.15s; color: #1e3a5f; font-family: var(--mono); }\n.logical-blog .pg-code { margin-top: 0.75rem; background: var(--code-bg); color: var(--code-text); font-family: var(--mono); font-size: 0.8rem; padding: 0.75rem 1rem; border-radius: 5px; line-height: 1.8; grid-column: 1 \/ -1; white-space: pre; }\n.logical-blog .migration-demo { display: flex; flex-direction: column; gap: 0.75rem; }\n.logical-blog .migration-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: 0.75rem; align-items: center; }\n.logical-blog .mig-before, .mig-after { font-family: var(--mono); font-size: 0.8rem; padding: 0.5rem 0.75rem; border-radius: 4px; line-height: 1.6; }\n.logical-blog .mig-before { background: #fee2e2; color: #991b1b; }\n.logical-blog .mig-after { background: #dcfce7; color: #166534; }\n.logical-blog .mig-arrow { text-align: center; font-size: 1.1rem; color: #888; }\n.logical-blog .support-badge { display: inline-block; font-family: var(--mono); font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 3px; font-weight: 500; }\n.logical-blog .support-badge.yes { background: #dcfce7; color: #166534; }\n.logical-blog .support-badge.partial { background: #fef3c7; color: #92400e; }\n.logical-blog .checklist { list-style: none; margin-left: 0; }\n.logical-blog .checklist li { padding: 0.25rem 0; }\n.logical-blog .checklist li::before { content: '\u2713 '; color: #16a34a; font-weight: 700; font-family: var(--mono); }\n.logical-blog .checklist.cross li::before { content: '\u2717 '; color: #dc2626; }\n@media (max-width: 620px) { .prop-viz, .dir-demo, .playground { grid-template-columns: 1fr; } .migration-row { grid-template-columns: 1fr; } .mig-arrow { display: none; } .writing-demo { grid-template-columns: 1fr 1fr; } }\n<\/style>\n<article class=\"logical-blog\">\n<section>\n<p>Picture this: you&#8217;ve built a beautiful card component. Padding looks perfect in English. Then your product manager says <em>&#8220;we need Arabic support&#8221;<\/em> and suddenly everything is mirrored &#8211; your left-aligned icon is now on the wrong side, your <code>margin-left<\/code> pushes content the wrong way, and you&#8217;re drowning in <code>[dir=\"rtl\"]<\/code> overrides that double your stylesheet size.<\/p>\n<p>Or imagine a different scenario: you&#8217;re adding vertical text to a Japanese language feature. Your <code>width<\/code> and <code>height<\/code> suddenly mean the wrong thing, and your carefully tuned padding no longer aligns with the text flow. You add more hacks. The codebase grows. The confidence shrinks.<\/p>\n<p><strong>CSS Logical Properties<\/strong> were designed specifically to kill these problems. Rather than anchoring spacing and sizing to fixed physical directions &#8211; top, bottom, left, right &#8211; they anchor them to the <em>flow of the document<\/em>. The browser resolves which physical direction that maps to at paint time, based on <code>writing-mode<\/code> and <code>direction<\/code>. Your CSS stays lean; every language just works.<\/p>\n<div class=\"callout\">\n      <strong>Core Idea<\/strong><br \/>\n      Logical properties describe positions relative to how content flows, not where it appears on screen. In English (LTR), <code>inline-start<\/code> resolves to <code>left<\/code>. In Arabic (RTL), it resolves to <code>right<\/code>. In Japanese vertical text, it resolves to <code>top<\/code>. The same CSS declaration handles all three with zero overrides.\n    <\/div>\n<p>This isn&#8217;t experimental. Logical properties have been in the CSS specification since 2017 and have full cross-browser support in all modern browsers. The question isn&#8217;t <em>whether<\/em> to use them &#8211; it&#8217;s building the mental model so they feel as natural as <code>margin-top<\/code>.<\/p>\n<\/section>\n<section>\n<h2>The Two Axes<\/h2>\n<p>The entire system of logical properties rests on two axes. Everything &#8211; margin, padding, border, sizing, positioning &#8211; gets expressed in terms of these two axes rather than physical screen directions.<\/p>\n<h3>Block Axis<\/h3>\n<p>The block axis runs <em>perpendicular<\/em> to the direction text flows in a line. In English, text flows left to right across a line, and new lines stack downward &#8211; so the block axis runs top to bottom. <code>block-start<\/code> means &#8220;the start of the block direction,&#8221; which in English is the top. <code>block-end<\/code> is the bottom.<\/p>\n<p>A useful way to think about it: <strong>block = the direction the page grows as you add more content.<\/strong> Paragraphs stack along the block axis. New items in a vertical list move along the block axis. Scroll (in a standard webpage) happens along the block axis.<\/p>\n<h3>Inline Axis<\/h3>\n<p>The inline axis runs <em>parallel<\/em> to the direction text flows within a line. In English (LTR), this is left to right. <code>inline-start<\/code> is the left edge where a line begins, <code>inline-end<\/code> is the right edge where it ends. In Arabic (RTL), these flip &#8211; <code>inline-start<\/code> is now on the right.<\/p>\n<p>Think of it as: <strong>inline = the direction a sentence flows.<\/strong> Characters in a word sit along the inline axis. Words in a sentence sit along the inline axis. The inline axis is where reading direction lives.<\/p>\n<p>These axes are <em>not<\/em> fixed to physical directions. They rotate and flip depending on the <code>writing-mode<\/code> and <code>direction<\/code> CSS properties. That&#8217;s the whole power of the system. The axes travel with the content flow, so your logical properties travel with them automatically &#8211; you write the CSS once, and the browser handles the geometry.<\/p>\n<div class=\"demo-wrap\">\n<div class=\"demo-header\">\n        <span>DEMO 01<\/span><br \/>\n        <span>Property Explorer &#8211; click any property to highlight its edge<\/span>\n      <\/div>\n<div class=\"demo-body\">\n<div class=\"prop-viz\">\n<div style=\"display: flex; flex-direction: column; align-items: center; gap: 0.75rem;\">\n<div style=\"font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: #999; text-align: center;\">Select a property \u2192<\/div>\n<div class=\"prop-viz-box\" id=\"viz-box\">\n              <span class=\"axis-label block-start\">block-start<\/span><br \/>\n              <span class=\"axis-label block-end\">block-end<\/span><br \/>\n              <span class=\"axis-label inline-start\">inline-start<\/span><br \/>\n              <span class=\"axis-label inline-end\">inline-end<\/span><\/p>\n<div class=\"center-text\" id=\"viz-center\" style=\"color:#aaa;\">\u2191 block<br \/>\u2190 inline \u2192<br \/>\u2193 block<\/div>\n<\/p><\/div>\n<div id=\"viz-desc\" style=\"font-family: var(--mono); font-size: 0.75rem; color: #666; text-align: center; max-width: 210px; line-height: 1.5; min-height: 2.5rem;\"><\/div>\n<\/p><\/div>\n<div class=\"prop-list\">\n<div class=\"prop-item block\" data-prop=\"margin-block-start\" data-physical=\"margin-top\" data-side=\"block-start\" data-desc=\"Adds space before the element in the block direction. Equivalent to margin-top in horizontal writing.\" onclick=\"selectProp(this)\">\n              <span>margin-block-start<\/span><span class=\"physical\">margin-top<\/span>\n            <\/div>\n<div class=\"prop-item block\" data-prop=\"margin-block-end\" data-physical=\"margin-bottom\" data-side=\"block-end\" data-desc=\"Adds space after the element in the block direction. Equivalent to margin-bottom in horizontal writing.\" onclick=\"selectProp(this)\">\n              <span>margin-block-end<\/span><span class=\"physical\">margin-bottom<\/span>\n            <\/div>\n<div class=\"prop-item inline\" data-prop=\"margin-inline-start\" data-physical=\"margin-left (LTR)\" data-side=\"inline-start\" data-desc=\"Space at the start of the inline direction. Left in LTR layouts, right in RTL layouts.\" onclick=\"selectProp(this)\">\n              <span>margin-inline-start<\/span><span class=\"physical\">margin-left<\/span>\n            <\/div>\n<div class=\"prop-item inline\" data-prop=\"margin-inline-end\" data-physical=\"margin-right (LTR)\" data-side=\"inline-end\" data-desc=\"Space at the end of the inline direction. Right in LTR layouts, left in RTL layouts.\" onclick=\"selectProp(this)\">\n              <span>margin-inline-end<\/span><span class=\"physical\">margin-right<\/span>\n            <\/div>\n<div class=\"prop-item block\" data-prop=\"padding-block\" data-physical=\"padding-top + padding-bottom\" data-side=\"block\" data-desc=\"Shorthand for both block-start and block-end padding. One value sets both; two values set start then end.\" onclick=\"selectProp(this)\">\n              <span>padding-block<\/span><span class=\"physical\">padding-top + -bottom<\/span>\n            <\/div>\n<div class=\"prop-item inline\" data-prop=\"padding-inline\" data-physical=\"padding-left + padding-right\" data-side=\"inline\" data-desc=\"Shorthand for both inline-start and inline-end padding. Replaces padding-left + padding-right in one go.\" onclick=\"selectProp(this)\">\n              <span>padding-inline<\/span><span class=\"physical\">padding-left + -right<\/span>\n            <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h3>Shorthands as a bonus<\/h3>\n<p>One underappreciated benefit of logical properties is how much more expressive the shorthand forms are. <code>padding-block: 1rem 2rem<\/code> sets block-start to <code>1rem<\/code> and block-end to <code>2rem<\/code> in a single declaration. The physical equivalent required two separate lines: <code>padding-top: 1rem; padding-bottom: 2rem;<\/code>. These shorthands aren&#8217;t just shorter &#8211; they group related values together and signal intent more clearly. <code>margin-inline: auto<\/code> communicates &#8220;center this horizontally in the text flow direction&#8221; better than the two-property version ever did.<\/p>\n<\/section>\n<section>\n<h2>Physical vs. Logical: Full Reference<\/h2>\n<p>Here&#8217;s the complete day-to-day mapping. Notice the consistent naming formula: <strong>[property]-[axis]-[side]<\/strong>. Pick a property family (<code>margin<\/code>, <code>padding<\/code>, <code>border<\/code>, <code>inset<\/code>), then an axis (<code>block<\/code> or <code>inline<\/code>), then optionally a side (<code>-start<\/code> or <code>-end<\/code>). Omit the side to get the shorthand for both sides of that axis. Once you internalize that pattern, you can derive any logical property name without memorizing a list. For a broader reference on all CSS properties &#8211; including the physical counterparts listed here &#8211; see our <a href=\"\/css-properties\/\" style=\"color: var(--accent2); text-decoration: underline; text-underline-offset: 3px;\">CSS properties reference<\/a>.<\/p>\n<h3>Spacing &#8211; Margin &amp; Padding<\/h3>\n<table class=\"mapping-table\">\n<thead>\n<tr>\n<th>Physical<\/th>\n<th>Logical Equivalent<\/th>\n<th>Shorthand<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code class=\"prop-physical\">margin-top<\/code><\/td>\n<td><code class=\"prop-logical\">margin-block-start<\/code><\/td>\n<td><code>margin-block: [start] [end]<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">margin-bottom<\/code><\/td>\n<td><code class=\"prop-logical\">margin-block-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">margin-left<\/code><\/td>\n<td><code class=\"prop-logical\">margin-inline-start<\/code><\/td>\n<td><code>margin-inline: [start] [end]<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">margin-right<\/code><\/td>\n<td><code class=\"prop-logical\">margin-inline-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">padding-top<\/code><\/td>\n<td><code class=\"prop-logical\">padding-block-start<\/code><\/td>\n<td><code>padding-block<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">padding-bottom<\/code><\/td>\n<td><code class=\"prop-logical\">padding-block-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">padding-left<\/code><\/td>\n<td><code class=\"prop-logical\">padding-inline-start<\/code><\/td>\n<td><code>padding-inline<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">padding-right<\/code><\/td>\n<td><code class=\"prop-logical\">padding-inline-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Borders<\/h3>\n<p>Border logical properties follow the same axis naming. Notably, <code>border-block<\/code> and <code>border-inline<\/code> let you style both edges of an axis simultaneously &#8211; something that had no physical equivalent before. Setting a consistent horizontal rule or a side-border on both left and right used to take two declarations; now it takes one.<\/p>\n<table class=\"mapping-table\">\n<thead>\n<tr>\n<th>Physical<\/th>\n<th>Logical Equivalent<\/th>\n<th>Shorthand<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code class=\"prop-physical\">border-top<\/code><\/td>\n<td><code class=\"prop-logical\">border-block-start<\/code><\/td>\n<td><code>border-block<\/code> (both edges)<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-bottom<\/code><\/td>\n<td><code class=\"prop-logical\">border-block-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-left<\/code><\/td>\n<td><code class=\"prop-logical\">border-inline-start<\/code><\/td>\n<td><code>border-inline<\/code> (both edges)<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-right<\/code><\/td>\n<td><code class=\"prop-logical\">border-inline-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-top-left-radius<\/code><\/td>\n<td><code class=\"prop-logical\">border-start-start-radius<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-top-right-radius<\/code><\/td>\n<td><code class=\"prop-logical\">border-start-end-radius<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-bottom-left-radius<\/code><\/td>\n<td><code class=\"prop-logical\">border-end-start-radius<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">border-bottom-right-radius<\/code><\/td>\n<td><code class=\"prop-logical\">border-end-end-radius<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"callout warn\">\n      <strong>Border-radius naming<\/strong><br \/>\n      The double-axis naming of logical border-radius (<code>border-start-start-radius<\/code>) trips people up at first. Read it as &#8220;the corner where block-start meets inline-start.&#8221; In LTR horizontal writing, that&#8217;s the top-left corner. In RTL, it&#8217;s the top-right. It&#8217;s consistent once you see the pattern, but it&#8217;s worth committing to memory separately from the rest.\n    <\/div>\n<h3>Sizing<\/h3>\n<p>This is where logical properties make the deepest conceptual sense. <code>width<\/code> is fundamentally the measurement along the text-flow direction &#8211; in horizontal writing that happens to be left-to-right, so <code>width<\/code> = horizontal. But in vertical writing modes like Japanese, what we&#8217;d colloquially call &#8220;width&#8221; is actually the block-axis measurement. <code>inline-size<\/code> cuts through this ambiguity: it always means &#8220;the size along the direction text flows in a line,&#8221; regardless of physical orientation.<\/p>\n<table class=\"mapping-table\">\n<thead>\n<tr>\n<th>Physical<\/th>\n<th>Logical Equivalent<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code class=\"prop-physical\">width<\/code><\/td>\n<td><code class=\"prop-logical\">inline-size<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">height<\/code><\/td>\n<td><code class=\"prop-logical\">block-size<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">min-width<\/code><\/td>\n<td><code class=\"prop-logical\">min-inline-size<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">max-width<\/code><\/td>\n<td><code class=\"prop-logical\">max-inline-size<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">min-height<\/code><\/td>\n<td><code class=\"prop-logical\">min-block-size<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">max-height<\/code><\/td>\n<td><code class=\"prop-logical\">max-block-size<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Positioning &#8211; Inset<\/h3>\n<p>The <code>inset<\/code> family covers <code>top<\/code>, <code>right<\/code>, <code>bottom<\/code>, <code>left<\/code> for positioned elements. The bare <code>inset<\/code> shorthand (without an axis qualifier) is itself a new addition &#8211; it sets all four physical sides at once, like a shorthand for <code>top + right + bottom + left<\/code>. It&#8217;s not purely logical, but it&#8217;s a useful shorthand that arrived alongside the logical property work.<\/p>\n<table class=\"mapping-table\">\n<thead>\n<tr>\n<th>Physical<\/th>\n<th>Logical Equivalent<\/th>\n<th>Shorthand<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code class=\"prop-physical\">top<\/code><\/td>\n<td><code class=\"prop-logical\">inset-block-start<\/code><\/td>\n<td><code>inset-block<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">bottom<\/code><\/td>\n<td><code class=\"prop-logical\">inset-block-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">left<\/code><\/td>\n<td><code class=\"prop-logical\">inset-inline-start<\/code><\/td>\n<td><code>inset-inline<\/code><\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">right<\/code><\/td>\n<td><code class=\"prop-logical\">inset-inline-end<\/code><\/td>\n<td>&#8211;<\/td>\n<\/tr>\n<tr>\n<td><code class=\"prop-physical\">top + right + bottom + left<\/code><\/td>\n<td>&#8211;<\/td>\n<td><code>inset<\/code> (all 4 sides)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/section>\n<section>\n<h2>The RTL Problem, Solved<\/h2>\n<p>Right-to-left support has historically been one of the most painful parts of internationalising a web application. The traditional approach involves writing all your CSS for LTR first, then appending a large <code>[dir=\"rtl\"]<\/code> block that mirrors every physical property. This approach has serious maintenance problems: the RTL block doubles your spacing-related CSS, it falls out of sync with changes to the base styles, and developers who don&#8217;t work in RTL languages often forget to update it &#8211; or never test it properly.<\/p>\n<p>The worst part is that this problem is entirely structural. You&#8217;re writing two sets of rules for what is conceptually one piece of information: &#8220;this element should have space on the reading-start side.&#8221; Logical properties let you express that concept once.<\/p>\n<h3>What the old approach looked like<\/h3>\n<pre><code><span class=\"comment\">\/* Base LTR styles *\/<\/span>\r\n<span class=\"sel\">.nav-icon<\/span> {\r\n  <span class=\"prop\">float<\/span>: <span class=\"val\">left<\/span>;\r\n  <span class=\"prop\">margin-right<\/span>: <span class=\"val\">0.75rem<\/span>;\r\n  <span class=\"prop\">padding-left<\/span>: <span class=\"val\">1rem<\/span>;\r\n  <span class=\"prop\">border-left<\/span>: <span class=\"val\">2px solid var(--brand)<\/span>;\r\n}\r\n\r\n<span class=\"comment\">\/* RTL override block - written separately, easy to forget to update *\/<\/span>\r\n<span class=\"sel\">[dir=\"rtl\"] .nav-icon<\/span> {\r\n  <span class=\"prop\">float<\/span>: <span class=\"val\">right<\/span>;\r\n  <span class=\"prop\">margin-right<\/span>: <span class=\"val\">0<\/span>;\r\n  <span class=\"prop\">margin-left<\/span>: <span class=\"val\">0.75rem<\/span>;\r\n  <span class=\"prop\">padding-left<\/span>: <span class=\"val\">0<\/span>;\r\n  <span class=\"prop\">padding-right<\/span>: <span class=\"val\">1rem<\/span>;\r\n  <span class=\"prop\">border-left<\/span>: <span class=\"val\">none<\/span>;\r\n  <span class=\"prop\">border-right<\/span>: <span class=\"val\">2px solid var(--brand)<\/span>;\r\n}<\/code><\/pre>\n<h3>The logical properties approach<\/h3>\n<pre><code><span class=\"comment\">\/* One rule set. Handles LTR and RTL. No override needed. *\/<\/span>\r\n<span class=\"sel\">.nav-icon<\/span> {\r\n  <span class=\"prop\">float<\/span>: <span class=\"val\">inline-start<\/span>;         <span class=\"comment\">\/* float also accepts logical values *\/<\/span>\r\n  <span class=\"prop\">margin-inline-end<\/span>: <span class=\"val\">0.75rem<\/span>;  <span class=\"comment\">\/* space after the icon in reading direction *\/<\/span>\r\n  <span class=\"prop\">padding-inline-start<\/span>: <span class=\"val\">1rem<\/span>;  <span class=\"comment\">\/* breathing room on the reading-start side *\/<\/span>\r\n  <span class=\"prop\">border-inline-start<\/span>: <span class=\"val\">2px solid var(--brand)<\/span>;\r\n}<\/code><\/pre>\n<div class=\"demo-wrap\">\n<div class=\"demo-header\">\n        <span>DEMO 02<\/span><br \/>\n        <span>Direction Toggle &#8211; physical vs. logical in action<\/span>\n      <\/div>\n<div class=\"demo-body\">\n<div class=\"dir-btn-group\">\n          <span style=\"font-family: var(--mono); font-size: 0.75rem; color: #999;\">Text direction:<\/span><br \/>\n          <button class=\"dir-btn active\" onclick=\"setDir('ltr', event)\">LTR (English)<\/button><br \/>\n          <button class=\"dir-btn\" onclick=\"setDir('rtl', event)\">RTL (Arabic)<\/button><br \/>\n          <button style=\"display:none\" class=\"dir-btn\" onclick=\"setDir('auto', event)\">\u27f3 Auto-flip<\/button>\n        <\/div>\n<div class=\"dir-demo\">\n<div>\n<div class=\"dir-label\">\u274c Physical properties<\/div>\n<div class=\"dir-box\" id=\"demo-physical\" dir=\"ltr\">\n<p style=\"font-size: 0.75rem; color: #999; margin-bottom: 0.5rem; font-family: var(--mono);\">margin-left \u00b7 border-left \u00b7 padding-left<\/p>\n<div class=\"inner-box\">\ud83d\udce6 Nav item. Has a left border accent and left-side padding. Switch to RTL to see it misalign.<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div>\n<div class=\"dir-label\">\u2705 Logical properties<\/div>\n<div class=\"dir-box\" id=\"demo-logical\" dir=\"ltr\">\n<p style=\"font-size: 0.75rem; color: #999; margin-bottom: 0.5rem; font-family: var(--mono);\">margin-inline-start \u00b7 border-inline-start \u00b7 padding-inline-start<\/p>\n<div class=\"inner-box\">\ud83d\udce6 Same visual. Border and padding follow inline-start &#8211; they flip correctly in RTL automatically.<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div id=\"dir-note\" style=\"margin-top: 1rem; font-family: var(--mono); font-size: 0.75rem; color: var(--accent); background: #fff5f5; padding: 0.6rem 0.9rem; border-radius: 4px; display: none; line-height: 1.6;\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h3>text-align and float also have logical values<\/h3>\n<p>It&#8217;s not just spacing properties. <code>text-align<\/code> accepts <code>start<\/code> and <code>end<\/code> as values. Using <code>text-align: start<\/code> instead of <code>text-align: left<\/code> means text aligns to the correct reading edge in both LTR and RTL without any override &#8211; and it reads more semantically accurately too. <code>float: inline-start<\/code> and <code>float: inline-end<\/code> work the same way. Even <code>resize: block<\/code> and <code>resize: inline<\/code> are available for <code>&lt;textarea&gt;<\/code> elements, constraining resizing to a specific axis.<\/p>\n<pre><code><span class=\"comment\">\/* text-align with logical values *\/<\/span>\r\n<span class=\"sel\">p<\/span>      { <span class=\"prop\">text-align<\/span>: <span class=\"val\">start<\/span>; }  <span class=\"comment\">\/* left in LTR, right in RTL *\/<\/span>\r\n<span class=\"sel\">.label<\/span> { <span class=\"prop\">text-align<\/span>: <span class=\"val\">end<\/span>; }    <span class=\"comment\">\/* right in LTR, left in RTL *\/<\/span>\r\n<span class=\"sel\">.hero<\/span>  { <span class=\"prop\">text-align<\/span>: <span class=\"val\">center<\/span>; } <span class=\"comment\">\/* center is already direction-agnostic *\/<\/span>\r\n\r\n<span class=\"comment\">\/* float with logical values *\/<\/span>\r\n<span class=\"sel\">.pull-quote<\/span> { <span class=\"prop\">float<\/span>: <span class=\"val\">inline-end<\/span>; } <span class=\"comment\">\/* right in LTR, left in RTL *\/<\/span><\/code><\/pre>\n<\/section>\n<section>\n<h2>Writing Modes &amp; Vertical Text<\/h2>\n<p>RTL is the more common internationalisation challenge for Western developers, but logical properties shine just as brightly in vertical writing systems. Japanese, Chinese, and Korean content is frequently laid out with <code>writing-mode: vertical-rl<\/code>, where text flows from top to bottom and columns progress right to left &#8211; used in traditional literature, newspapers, and many modern Japanese UI designs. Mongolian traditionally uses <code>writing-mode: vertical-lr<\/code>, where columns progress left to right instead.<\/p>\n<p>In a vertical writing mode, the axes literally rotate. The block axis now runs <em>horizontally<\/em> &#8211; because that&#8217;s the direction new columns stack. The inline axis runs <em>vertically<\/em> &#8211; because that&#8217;s the direction text flows within a column. If you&#8217;ve used physical properties throughout your CSS, you&#8217;d need to completely rethink your spacing for any vertical-mode component. With logical properties, the rotation is handled automatically by the browser &#8211; your <code>padding-inline<\/code> gives space in the vertical direction when the writing mode is vertical, exactly as it should.<\/p>\n<div class=\"demo-wrap\">\n<div class=\"demo-header\">\n        <span>DEMO 03<\/span><br \/>\n        <span>Writing Modes &#8211; how the axes shift<\/span>\n      <\/div>\n<div class=\"demo-body\">\n<p style=\"font-family: var(--mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: #888; margin-bottom: 1rem;\">Click a writing mode to see how the block\/inline axes map \u2192<\/p>\n<div class=\"writing-demo\">\n<div class=\"writing-card active\" onclick=\"setWritingMode('horizontal-tb', 'ltr', this)\">\n<div class=\"writing-card-header\">horizontal-tb \u00b7 LTR<\/div>\n<div class=\"writing-card-body\">\n<div class=\"writing-sample\">Hello! Standard English layout.<\/div>\n<\/div><\/div>\n<div class=\"writing-card\" onclick=\"setWritingMode('horizontal-tb', 'rtl', this)\">\n<div class=\"writing-card-header\">horizontal-tb \u00b7 RTL<\/div>\n<div class=\"writing-card-body\">\n<div class=\"writing-sample\" dir=\"rtl\">\u0645\u0631\u062d\u0628\u0627! Arabic: inline-start = right.<\/div>\n<\/div><\/div>\n<div class=\"writing-card\" onclick=\"setWritingMode('vertical-rl', 'ltr', this)\">\n<div class=\"writing-card-header\">vertical-rl<\/div>\n<div class=\"writing-card-body\" style=\"justify-content: center; min-height: 130px;\">\n<div class=\"writing-sample\" style=\"writing-mode: vertical-rl;\">\u7e26\u66f8\u304d: Japanese vertical text flows top to bottom.<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div class=\"writing-card\" onclick=\"setWritingMode('vertical-lr', 'ltr', this)\">\n<div class=\"writing-card-header\">vertical-lr<\/div>\n<div class=\"writing-card-body\" style=\"justify-content: center; min-height: 130px;\">\n<div class=\"writing-sample\" style=\"writing-mode: vertical-lr;\">\u182e\u1823\u1829\u182d\u1823\u182f: Mongolian, columns progress left to right.<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div id=\"wm-info\" style=\"margin-top: 1rem; padding: 0.75rem 1rem; background: #f5f5f5; border-radius: 4px; font-family: var(--mono); font-size: 0.8rem; color: #444; line-height: 1.7;\">\n          In <strong>horizontal-tb LTR<\/strong>: block-start = top \u00b7 block-end = bottom \u00b7 inline-start = left \u00b7 inline-end = right\n        <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h3>A concrete vertical text example<\/h3>\n<p>Say you&#8217;re building a Japanese article layout with a sidebar label that uses <code>writing-mode: vertical-rl<\/code>. With physical properties, you&#8217;d set <code>padding-left<\/code> and <code>padding-right<\/code> for breathing room. But in vertical writing, &#8220;left&#8221; and &#8220;right&#8221; are the block axis &#8211; they control the depth of columns, not the spacing of text within a line. Your padding is suddenly applied in the wrong dimension. With logical properties, <code>padding-inline<\/code> always gives space in the text-flow direction, regardless of what physical direction that currently maps to.<\/p>\n<pre><code><span class=\"sel\">.sidebar-label<\/span> {\r\n  <span class=\"prop\">writing-mode<\/span>: <span class=\"val\">vertical-rl<\/span>;\r\n\r\n  <span class=\"comment\">\/* \u274c Physical - applies space in the block direction (horizontal), not inline *\/<\/span>\r\n  <span class=\"prop\">padding-left<\/span>: <span class=\"val\">1rem<\/span>;\r\n  <span class=\"prop\">padding-right<\/span>: <span class=\"val\">1rem<\/span>;\r\n\r\n  <span class=\"comment\">\/* \u2705 Logical - always gives space in the text-flow direction *\/<\/span>\r\n  <span class=\"prop\">padding-inline<\/span>: <span class=\"val\">1rem<\/span>;  <span class=\"comment\">\/* = top + bottom in vertical-rl *\/<\/span>\r\n  <span class=\"prop\">padding-block<\/span>: <span class=\"val\">0.5rem<\/span>; <span class=\"comment\">\/* = left + right in vertical-rl *\/<\/span>\r\n}<\/code><\/pre>\n<\/section>\n<section>\n<h2>Live Playground<\/h2>\n<p>The best way to build intuition for logical properties is to manipulate them and watch the box model update in real time. Use the sliders below to adjust padding, border, and margin on different logical axes. Notice how the generated CSS uses shorthands where values are equal &#8211; the same way you&#8217;d write it in production.<\/p>\n<div class=\"demo-wrap\">\n<div class=\"demo-header\">\n        <span>DEMO 04<\/span><br \/>\n        <span>Logical Box Model Builder<\/span>\n      <\/div>\n<div class=\"demo-body\">\n<div class=\"playground\">\n<div class=\"playground-controls\">\n<div class=\"control-row\">\n              <span class=\"control-label\">padding-block-start<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"48\" value=\"16\" id=\"pbs\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"pbs-val\">16px<\/span>\n            <\/div>\n<div class=\"control-row\">\n              <span class=\"control-label\">padding-block-end<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"48\" value=\"16\" id=\"pbe\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"pbe-val\">16px<\/span>\n            <\/div>\n<div class=\"control-row\">\n              <span class=\"control-label\">padding-inline-start<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"64\" value=\"24\" id=\"pis\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"pis-val\">24px<\/span>\n            <\/div>\n<div class=\"control-row\">\n              <span class=\"control-label\">padding-inline-end<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"64\" value=\"24\" id=\"pie\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"pie-val\">24px<\/span>\n            <\/div>\n<div class=\"control-row\">\n              <span class=\"control-label\">border-inline-start<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"12\" value=\"3\" id=\"bis\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"bis-val\">3px<\/span>\n            <\/div>\n<div class=\"control-row\">\n              <span class=\"control-label\">margin-inline-start<\/span><br \/>\n              <input type=\"range\" min=\"0\" max=\"48\" value=\"0\" id=\"mis\" oninput=\"updatePlayground()\"><br \/>\n              <span class=\"control-val\" id=\"mis-val\">0px<\/span>\n            <\/div>\n<\/p><\/div>\n<div class=\"playground-preview\">\n<div class=\"playground-box\" id=\"pg-box\">Component text<\/div>\n<\/p><\/div>\n<div class=\"pg-code\" id=\"pg-code\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/section>\n<section>\n<h2>Practical Example: A Real Card Component<\/h2>\n<p>Theory is useful, but let&#8217;s build something real. Here&#8217;s a notification card component &#8211; the kind you&#8217;d find in a design system &#8211; written entirely with logical properties. It renders correctly in LTR, RTL, and vertical writing modes with zero additional CSS.<\/p>\n<pre><code><span class=\"sel\">.notification-card<\/span> {\r\n  <span class=\"comment\">\/* Sizing: inline-size instead of width *\/<\/span>\r\n  <span class=\"prop\">inline-size<\/span>: <span class=\"val\">100%<\/span>;\r\n  <span class=\"prop\">max-inline-size<\/span>: <span class=\"val\">420px<\/span>;\r\n\r\n  <span class=\"comment\">\/* Padding: block for vertical rhythm, inline for horizontal breathing room *\/<\/span>\r\n  <span class=\"prop\">padding-block<\/span>: <span class=\"val\">1.25rem<\/span>;\r\n  <span class=\"prop\">padding-inline<\/span>: <span class=\"val\">1.5rem<\/span>;\r\n\r\n  <span class=\"comment\">\/* Accent border on the reading-start edge *\/<\/span>\r\n  <span class=\"prop\">border-inline-start<\/span>: <span class=\"val\">4px solid var(--brand)<\/span>;\r\n\r\n  <span class=\"comment\">\/* Round only the end-side corners (the side without the accent border) *\/<\/span>\r\n  <span class=\"prop\">border-start-end-radius<\/span>: <span class=\"val\">8px<\/span>;\r\n  <span class=\"prop\">border-end-end-radius<\/span>: <span class=\"val\">8px<\/span>;\r\n\r\n  position: relative;\r\n}\r\n\r\n<span class=\"sel\">.notification-card__icon<\/span> {\r\n  <span class=\"comment\">\/* Space between icon and text, on the end side of the icon *\/<\/span>\r\n  <span class=\"prop\">margin-inline-end<\/span>: <span class=\"val\">0.75rem<\/span>;\r\n}\r\n\r\n<span class=\"sel\">.notification-card__badge<\/span> {\r\n  position: absolute;\r\n  <span class=\"comment\">\/* Top-right corner in LTR, top-left in RTL - no override needed *\/<\/span>\r\n  <span class=\"prop\">inset-block-start<\/span>: <span class=\"val\">0.75rem<\/span>;\r\n  <span class=\"prop\">inset-inline-end<\/span>: <span class=\"val\">0.75rem<\/span>;\r\n}\r\n\r\n<span class=\"sel\">.notification-card__timestamp<\/span> {\r\n  display: block;\r\n  <span class=\"prop\">margin-block-start<\/span>: <span class=\"val\">0.5rem<\/span>; <span class=\"comment\">\/* space above, below the body text *\/<\/span>\r\n  <span class=\"prop\">text-align<\/span>: <span class=\"val\">end<\/span>;             <span class=\"comment\">\/* flush with reading-end edge *\/<\/span>\r\n}<\/code><\/pre>\n<h3>The centering pattern to retire immediately<\/h3>\n<p>The single most impactful quick win in any codebase is replacing the centering pattern. How many times have you written <code>margin-left: auto; margin-right: auto<\/code>? Two lines, both required, physically anchored. Replace both with <code>margin-inline: auto<\/code>. It&#8217;s shorter, works in any writing mode, and clearly signals intent: &#8220;distribute available inline space equally on both sides.&#8221;<\/p>\n<pre><code><span class=\"comment\">\/* Old way - two properties, physically named *\/<\/span>\r\n<span class=\"sel\">.centered<\/span> {\r\n  <span class=\"prop\">margin-left<\/span>: <span class=\"val\">auto<\/span>;\r\n  <span class=\"prop\">margin-right<\/span>: <span class=\"val\">auto<\/span>;\r\n}\r\n\r\n<span class=\"comment\">\/* New way - one property, direction-agnostic *\/<\/span>\r\n<span class=\"sel\">.centered<\/span> {\r\n  <span class=\"prop\">margin-inline<\/span>: <span class=\"val\">auto<\/span>;\r\n}<\/code><\/pre>\n<h3>The border-radius corner naming<\/h3>\n<p>Logical border-radius names use a double-axis convention that takes a moment to learn. <code>border-start-start-radius<\/code> names the corner where <em>block-start meets inline-start<\/em> &#8211; in English, that&#8217;s the top-left. <code>border-start-end-radius<\/code> is where block-start meets inline-end &#8211; the top-right in LTR. It&#8217;s verbose, but it&#8217;s completely consistent with the rest of the system, and it means a &#8220;tab&#8221; UI component (rounded on two adjacent corners) automatically mirrors itself in RTL.<\/p>\n<\/section>\n<section>\n<h2>Migrating an Existing Codebase<\/h2>\n<p>You don&#8217;t need to rewrite your entire stylesheet in one sitting. The best strategy is incremental: adopt logical properties as you touch existing code, write all new code with logical properties by default, and use tooling to enforce the convention going forward. Here&#8217;s how to do each step without disrupting production.<\/p>\n<h3>Step 1 &#8211; Start with symmetric patterns<\/h3>\n<p>The safest starting point is any property that&#8217;s currently set symmetrically &#8211; same value on both left and right, or both top and bottom. These migrations are trivially correct and visually identical in LTR. If you&#8217;d rather not do it by hand, our <a href=\"\/css-logical-property-converter\/\" style=\"color: var(--accent2); text-decoration: underline; text-underline-offset: 3px;\">CSS logical property converter<\/a> can translate physical properties to their logical equivalents automatically:<\/p>\n<div class=\"demo-wrap\">\n<div class=\"demo-header\"><span>Common migrations<\/span><span>Physical \u2192 Logical<\/span><\/div>\n<div class=\"demo-body\">\n<div class=\"migration-demo\">\n<div class=\"migration-row\">\n<div class=\"mig-before\">margin-left: auto;<br \/>margin-right: auto;<\/div>\n<div class=\"mig-arrow\">\u2192<\/div>\n<div class=\"mig-after\">margin-inline: auto;<\/div>\n<\/p><\/div>\n<div class=\"migration-row\">\n<div class=\"mig-before\">padding-top: 1rem;<br \/>padding-bottom: 1rem;<\/div>\n<div class=\"mig-arrow\">\u2192<\/div>\n<div class=\"mig-after\">padding-block: 1rem;<\/div>\n<\/p><\/div>\n<div class=\"migration-row\">\n<div class=\"mig-before\">padding-left: 1.5rem;<br \/>padding-right: 1.5rem;<\/div>\n<div class=\"mig-arrow\">\u2192<\/div>\n<div class=\"mig-after\">padding-inline: 1.5rem;<\/div>\n<\/p><\/div>\n<div class=\"migration-row\">\n<div class=\"mig-before\">width: 100%;<br \/>max-width: 800px;<\/div>\n<div class=\"mig-arrow\">\u2192<\/div>\n<div class=\"mig-after\">inline-size: 100%;<br \/>max-inline-size: 800px;<\/div>\n<\/p><\/div>\n<div class=\"migration-row\">\n<div class=\"mig-before\">text-align: left;<\/div>\n<div class=\"mig-arrow\">\u2192<\/div>\n<div class=\"mig-after\">text-align: start;<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h3>Step 2 &#8211; Think about directional intent<\/h3>\n<p>Not every physical property should become a logical one. Before converting, ask: <em>&#8220;Should this direction flip in an RTL or vertical layout?&#8221;<\/em> A decorative swoosh that is always pinned to the left side of the viewport as a visual brand element? That should stay as <code>left: 0<\/code>. A &#8220;back to top&#8221; button that is always in the bottom-right regardless of language? Keep <code>right: 1rem; bottom: 1rem<\/code>. The rule is simple: physical properties for physical design decisions, logical properties for flow-relative ones.<\/p>\n<div class=\"callout warn\">\n      <strong>Don&#8217;t blindly convert everything<\/strong><br \/>\n      A notification tray that is always on the right side of the screen in your product design should use <code>right: 0<\/code>, not <code>inset-inline-end: 0<\/code>. Using the logical version would move it to the left side in RTL &#8211; which would violate the intended layout. Use logical properties where direction <em>follows content<\/em>, not where direction is a fixed product decision.\n    <\/div>\n<h3>Step 3 &#8211; Update your design tokens<\/h3>\n<p>If you&#8217;re using CSS custom properties or a design token system, this is a good moment to audit your token names. Tokens named <code>--spacing-left-sm<\/code> encode physical direction into the token name itself. Renaming to <code>--spacing-inline-start-sm<\/code> &#8211; or more practically, restructuring so the &#8220;which side&#8221; decision happens in the component rather than the token &#8211; aligns your token architecture with logical thinking from the start.<\/p>\n<\/section>\n<section>\n<h2>Browser Support<\/h2>\n<p>Logical properties are not experimental or behind flags. They&#8217;ve been shipping in stable browsers since 2017\u20132020 depending on the property group, and as of 2025 they have near-universal support across all evergreen browsers. The table below reflects stable release support.<\/p>\n<table class=\"mapping-table\">\n<thead>\n<tr>\n<th>Property Group<\/th>\n<th>Chrome<\/th>\n<th>Firefox<\/th>\n<th>Safari<\/th>\n<th>Edge<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>margin \/ padding logical<\/td>\n<td><span class=\"support-badge yes\">\u2713 69+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 41+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 12.1+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 79+<\/span><\/td>\n<\/tr>\n<tr>\n<td>border logical<\/td>\n<td><span class=\"support-badge yes\">\u2713 87+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 41+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 15+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 87+<\/span><\/td>\n<\/tr>\n<tr>\n<td>border-radius logical<\/td>\n<td><span class=\"support-badge yes\">\u2713 89+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 66+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 15+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 89+<\/span><\/td>\n<\/tr>\n<tr>\n<td>inset logical<\/td>\n<td><span class=\"support-badge yes\">\u2713 87+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 63+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 14.1+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 87+<\/span><\/td>\n<\/tr>\n<tr>\n<td>inline-size \/ block-size<\/td>\n<td><span class=\"support-badge yes\">\u2713 57+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 41+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 12.1+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 79+<\/span><\/td>\n<\/tr>\n<tr>\n<td>text-align: start \/ end<\/td>\n<td><span class=\"support-badge yes\">\u2713 All<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 All<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 All<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 All<\/span><\/td>\n<\/tr>\n<tr>\n<td>float: inline-start \/ end<\/td>\n<td><span class=\"support-badge yes\">\u2713 118+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 55+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 15+<\/span><\/td>\n<td><span class=\"support-badge yes\">\u2713 118+<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Internet Explorer has no support and reached end-of-life in June 2022. If you&#8217;re still required to support IE, logical properties need physical fallbacks &#8211; but if you&#8217;re free of that constraint, you can use logical properties without any fallback in modern browsers. For teams that need to support Safari 12\u201314 specifically (the gap years for some property groups), physical property fallbacks written above the logical declaration will cascade correctly:<\/p>\n<pre><code><span class=\"comment\">\/* Safe pattern for wider support where needed *\/<\/span>\r\n<span class=\"sel\">.element<\/span> {\r\n  <span class=\"prop\">margin-left<\/span>: <span class=\"val\">1.5rem<\/span>;           <span class=\"comment\">\/* fallback for older browsers *\/<\/span>\r\n  <span class=\"prop\">margin-inline-start<\/span>: <span class=\"val\">1.5rem<\/span>;  <span class=\"comment\">\/* overrides in supporting browsers *\/<\/span>\r\n}<\/code><\/pre>\n<\/section>\n<section>\n<h2>When Should You Use Them?<\/h2>\n<p>For any CSS written today, logical properties should be the default. The support is there, the DX is better (shorthands reduce line count, names signal intent more clearly), and the resilience to internationalisation changes is built in from the start. The honest question isn&#8217;t whether to use them &#8211; it&#8217;s how to make them feel as natural as the physical properties you&#8217;ve been writing for years.<\/p>\n<h3>Default to logical properties for\u2026<\/h3>\n<ul class=\"checklist\">\n<li>All spacing that should flip in RTL layouts (margin, padding, border)<\/li>\n<li>Sizing that should adapt to writing-mode changes (use inline-size, block-size)<\/li>\n<li>Any new component in a design system<\/li>\n<li>Positioning UI elements relative to text flow (icons, badges, timestamps, carets)<\/li>\n<li>Centering elements horizontally &#8211; <code>margin-inline: auto<\/code> is strictly better<\/li>\n<li>Text alignment where reading direction should determine the edge<\/li>\n<\/ul>\n<h3>Keep physical properties for\u2026<\/h3>\n<ul class=\"checklist cross\">\n<li>Elements intentionally anchored to a fixed screen edge (viewport overlays, fixed sidebars always on the right)<\/li>\n<li>Physical canvas, SVG, or WebGL coordinates where logical properties don&#8217;t apply<\/li>\n<li>Explicit design decisions that should not mirror (brand-specific decorative layouts)<\/li>\n<\/ul>\n<div class=\"callout tip\">\n      <strong>Quick Decision Rule<\/strong><br \/>\n      Ask: <em>&#8220;Should this spacing or position flip if the user switches to Arabic or Japanese vertical text?&#8221;<\/em> If yes, or if you&#8217;re unsure &#8211; use a logical property. Physical properties are for deliberate, direction-fixed design decisions. When in doubt, go logical.\n    <\/div>\n<section style=\"border-top: 2px solid var(--ink); padding-top: 2.5rem; margin-top: 4rem;\">\n<h2>TL;DR<\/h2>\n<p>CSS Logical Properties replace physical directions (<code>top<\/code>, <code>left<\/code>, <code>right<\/code>, <code>bottom<\/code>) with flow-relative equivalents (<code>block-start<\/code>, <code>inline-start<\/code>, etc.) that automatically adapt to any writing direction or mode. They eliminate RTL override blocks, make vertical-text layouts correct by default, and produce shorter, more semantically accurate CSS in the process. Browser support is universal across all modern browsers. Migration is safe and incremental. Tooling &#8211; Tailwind, ESLint plugins, CSS-in-JS &#8211; all support them without friction.<\/p>\n<p>The next time you reach for <code>padding-left<\/code>, pause and ask if <code>padding-inline-start<\/code> better describes your intent. Nine times out of ten, it does.<\/p>\n<\/section>\n<\/article>\n<p><script>\n  \/\/ \u2500\u2500 DEMO 1: Property Visualizer \u2500\u2500\n  const vizBox = document.getElementById('viz-box');\n  const vizCenter = document.getElementById('viz-center');\n  const vizDesc = document.getElementById('viz-desc');\n  function selectProp(el) {\n    document.querySelectorAll('.prop-item').forEach(i => i.classList.remove('active'));\n    el.classList.add('active');\n    const { side, prop, physical, desc } = el.dataset;\n    const isInline = el.classList.contains('inline');\n    const color = isInline ? '#2563eb' : '#c84b2f';\n    const bg = isInline ? '#eff6ff' : '#fff5f5';\n    vizBox.style.borderTop = vizBox.style.borderBottom = vizBox.style.borderLeft = vizBox.style.borderRight = '2px solid #ddd';\n    vizBox.style.backgroundColor = bg;\n    vizBox.style.borderRadius = '4px';\n    const map = {\n      'block-start':  () => vizBox.style.borderTop    = `4px solid ${color}`,\n      'block-end':    () => vizBox.style.borderBottom = `4px solid ${color}`,\n      'inline-start': () => vizBox.style.borderLeft   = `4px solid ${color}`,\n      'inline-end':   () => vizBox.style.borderRight  = `4px solid ${color}`,\n      'block':        () => { vizBox.style.borderTop  = `4px solid ${color}`; vizBox.style.borderBottom = `4px solid ${color}`; },\n      'inline':       () => { vizBox.style.borderLeft = `4px solid ${color}`; vizBox.style.borderRight  = `4px solid ${color}`; },\n    };\n    if (map[side]) map[side]();\n    vizCenter.innerHTML = `<strong style=\"color:${color}\">${prop}<\/strong><br \/><span style=\"font-size:0.7rem;color:#888\">= ${physical}<\/span>`;\n    vizDesc.textContent = desc;\n    document.querySelectorAll('.axis-label').forEach(l => l.style.background = bg);\n  }\n  \/\/ \u2500\u2500 DEMO 2: RTL Toggle \u2500\u2500\n  let currentDir = 'ltr';\n  let autoInterval = null;\n  function setDir(dir, e) {\n    if (autoInterval) { clearInterval(autoInterval); autoInterval = null; }\n    document.querySelectorAll('.dir-btn').forEach(b => b.classList.remove('active'));\n    e.target.classList.add('active');\n    if (dir === 'auto') {\n      currentDir = currentDir === 'ltr' ? 'rtl' : 'ltr';\n      applyDir(currentDir);\n      autoInterval = setInterval(() => {\n        currentDir = currentDir === 'ltr' ? 'rtl' : 'ltr';\n        applyDir(currentDir);\n      }, 2200);\n    } else {\n      currentDir = dir;\n      applyDir(dir);\n    }\n  }\n  function applyDir(dir) {\n    document.getElementById('demo-physical').dir = dir;\n    document.getElementById('demo-logical').dir = dir;\n    const note = document.getElementById('dir-note');\n    if (dir === 'rtl') {\n      note.style.display = 'block';\n      note.innerHTML = '\u26a0 RTL active - the red box (physical properties) has its border and indent pushed to the wrong side. The blue box (logical properties) mirrors correctly with no additional CSS.';\n    } else {\n      note.style.display = 'none';\n    }\n  }\n  \/\/ \u2500\u2500 DEMO 3: Writing Mode \u2500\u2500\n  const wmInfoMap = {\n    'horizontal-tb-ltr': 'In <strong>horizontal-tb LTR<\/strong>: block-start = top &nbsp;\u00b7&nbsp; block-end = bottom &nbsp;\u00b7&nbsp; inline-start = left &nbsp;\u00b7&nbsp; inline-end = right',\n    'horizontal-tb-rtl': 'In <strong>horizontal-tb RTL<\/strong>: block-start = top &nbsp;\u00b7&nbsp; block-end = bottom &nbsp;\u00b7&nbsp; inline-start = <em>right<\/em> &nbsp;\u00b7&nbsp; inline-end = <em>left<\/em>. The inline axis is flipped.',\n    'vertical-rl-ltr':   'In <strong>vertical-rl<\/strong>: text flows top\u2192bottom, columns right\u2192left. block-start = right &nbsp;\u00b7&nbsp; block-end = left &nbsp;\u00b7&nbsp; inline-start = top &nbsp;\u00b7&nbsp; inline-end = bottom.',\n    'vertical-lr-ltr':   'In <strong>vertical-lr<\/strong>: text flows top\u2192bottom, columns left\u2192right. block-start = left &nbsp;\u00b7&nbsp; block-end = right &nbsp;\u00b7&nbsp; inline-start = top &nbsp;\u00b7&nbsp; inline-end = bottom.',\n  };\n  function setWritingMode(mode, dir, card) {\n    document.querySelectorAll('.writing-card').forEach(c => c.classList.remove('active'));\n    card.classList.add('active');\n    const key = `${mode}-${dir}`;\n    const infoEl = document.getElementById('wm-info');\n    if (wmInfoMap[key]) infoEl.innerHTML = wmInfoMap[key];\n  }\n  \/\/ \u2500\u2500 DEMO 4: Playground \u2500\u2500\n  function updatePlayground() {\n    const pbs = +document.getElementById('pbs').value;\n    const pbe = +document.getElementById('pbe').value;\n    const pis = +document.getElementById('pis').value;\n    const pie = +document.getElementById('pie').value;\n    const bis = +document.getElementById('bis').value;\n    const mis = +document.getElementById('mis').value;\n    document.getElementById('pbs-val').textContent = pbs + 'px';\n    document.getElementById('pbe-val').textContent = pbe + 'px';\n    document.getElementById('pis-val').textContent = pis + 'px';\n    document.getElementById('pie-val').textContent = pie + 'px';\n    document.getElementById('bis-val').textContent = bis + 'px';\n    document.getElementById('mis-val').textContent = mis + 'px';\n    const box = document.getElementById('pg-box');\n    box.style.paddingBlockStart  = pbs + 'px';\n    box.style.paddingBlockEnd    = pbe + 'px';\n    box.style.paddingInlineStart = pis + 'px';\n    box.style.paddingInlineEnd   = pie + 'px';\n    box.style.borderInlineStart  = bis + 'px solid #2563eb';\n    box.style.marginInlineStart  = mis + 'px';\n    const lines = [];\n    if (bis > 0) lines.push(`border-inline-start: ${bis}px solid #2563eb;`);\n    if (mis > 0) lines.push(`margin-inline-start: ${mis}px;`);\n    if (pbs === pbe) {\n      lines.push(`padding-block: ${pbs}px;`);\n    } else {\n      lines.push(`padding-block-start: ${pbs}px;`);\n      lines.push(`padding-block-end: ${pbe}px;`);\n    }\n    if (pis === pie) {\n      lines.push(`padding-inline: ${pis}px;`);\n    } else {\n      lines.push(`padding-inline-start: ${pis}px;`);\n      lines.push(`padding-inline-end: ${pie}px;`);\n    }\n    document.getElementById('pg-code').textContent = lines.join('\\n');\n  }\n  updatePlayground();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Picture this: you&#8217;ve built a beautiful card component. Padding looks perfect in English. Then your product manager says &#8220;we need Arabic support&#8221; and suddenly everything is mirrored &#8211; your left-aligned icon is now on the wrong side, your margin-left pushes content the wrong way, and you&#8217;re drowning in [dir=&#8221;rtl&#8221;] overrides that double your stylesheet size. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":802,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-801","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-css","wpautop"],"_links":{"self":[{"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/posts\/801","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/comments?post=801"}],"version-history":[{"count":1,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/posts\/801\/revisions"}],"predecessor-version":[{"id":803,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/posts\/801\/revisions\/803"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/media\/802"}],"wp:attachment":[{"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/media?parent=801"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/categories?post=801"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cssportal.com\/blog\/wp-json\/wp\/v2\/tags?post=801"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}