[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-css-logical-properties":32},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"slug":33,"title":34,"summary":35,"html":36,"css":37,"js":38,"config":39,"doc":41},"css-logical-properties","Logical properties — flip the layout to RTL, watch it hold",null,"\u003Cdiv class=\"wrap\">\n    \u003Ch1>🎮 Logical properties: flip to RTL, watch it hold\u003C\u002Fh1>\n    \u003Cp class=\"sub\">\n      A sidebar and a content card with an accent border, a gap, and left-aligned text. Switch the layout to\n      \u003Cstrong>RTL\u003C\u002Fstrong> (Arabic, Hebrew, Persian) while using \u003Cstrong>physical\u003C\u002Fstrong> CSS\n      (\u003Ccode>margin-right\u003C\u002Fcode>, \u003Ccode>border-left\u003C\u002Fcode>, \u003Ccode>text-align: left\u003C\u002Fcode>) — everything stays glued to the\n      wrong edge. Switch to \u003Cstrong>logical\u003C\u002Fstrong> CSS and it mirrors automatically. Same rules, both directions.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>1 · Controls\u003C\u002Fh2>\n      \u003Cdiv class=\"controls\">\n        \u003Cdiv class=\"ctl\">\n          \u003Cspan>Writing direction\u003C\u002Fspan>\n          \u003Cdiv class=\"seg\" id=\"dir\">\n            \u003Cbutton data-dir=\"ltr\" aria-pressed=\"true\">LTR (English)\u003C\u002Fbutton>\n            \u003Cbutton data-dir=\"rtl\" aria-pressed=\"false\">RTL (العربية)\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"ctl\">\n          \u003Cspan>CSS model\u003C\u002Fspan>\n          \u003Cdiv class=\"seg\" id=\"model\">\n            \u003Cbutton data-model=\"physical\" aria-pressed=\"true\">Physical\u003C\u002Fbutton>\n            \u003Cbutton data-model=\"logical\" aria-pressed=\"false\">Logical\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>2 · The layout\u003C\u002Fh2>\n      \u003Cdiv class=\"demo\">\n        \u003Cdiv class=\"layout physical\" id=\"demo\" dir=\"ltr\">\n          \u003Caside class=\"sidebar\">Sidebar\u003Cbr>nav · filters · links\u003C\u002Faside>\n          \u003Cdiv class=\"content\">\n            \u003Cspan class=\"badge-new\">NEW\u003C\u002Fspan>\n            \u003Ch3 id=\"hd\">Logical properties\u003C\u002Fh3>\n            \u003Cp id=\"bd\">This card has an accent border on the reading-start edge, a gap before the sidebar, and text aligned to where reading begins. Watch which edge each one clings to.\u003C\u002Fp>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"verdict bad\" id=\"verdict\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>3 · The CSS applied\u003C\u002Fh2>\n      \u003Cdiv class=\"code\" id=\"code\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>",":root {\n    --bg: #0d1117; --panel: #161b22; --ink: #e8ebf2; --muted: #9aa4b2;\n    --accent: #7c9cff; --good: #3fb950; --bad: #f0883e; --line: #232a35;\n    --card: #0b0e14;\n    font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n  }\n  * { box-sizing: border-box; }\n  body { margin: 0; background: var(--bg); color: var(--ink); min-height: 100vh;\n    display: flex; align-items: flex-start; justify-content: center; padding: 18px; }\n  .wrap { width: 100%; max-width: 820px; }\n\n  h1 { font-size: 1.2rem; margin: 0 0 3px; letter-spacing: -0.01em; }\n  .sub { color: var(--muted); font-size: .9rem; margin: 0 0 14px; line-height: 1.45; }\n  .sub code, .sub strong { color: var(--accent); }\n  .sub code { background: #ffffff10; padding: 1px 5px; border-radius: 5px; font-size: .85em;\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n  .sub strong { font-weight: 700; }\n\n  .panel { background: var(--panel); border: 1px solid var(--line);\n    border-radius: 14px; padding: 16px; margin-bottom: 14px; }\n  .panel h2 { font-size: .72rem; text-transform: uppercase; letter-spacing: .07em;\n    color: var(--muted); margin: 0 0 12px; font-weight: 700; }\n\n  \u002F* ---- Segmented controls ---- *\u002F\n  .controls { display: flex; flex-wrap: wrap; gap: 16px 22px; align-items: center; }\n  .ctl { display: flex; flex-direction: column; gap: 6px; }\n  .ctl > span { font-size: .72rem; color: var(--muted); font-weight: 700; letter-spacing: .03em;\n    text-transform: uppercase; }\n  .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }\n  .seg button { font: inherit; font-weight: 600; font-size: .84rem; border: none; cursor: pointer;\n    background: transparent; color: var(--muted); padding: 8px 14px; }\n  .seg button:hover { background: #ffffff08; color: var(--ink); }\n  .seg button[aria-pressed=\"true\"] { background: var(--accent); color: #0b0e14; }\n  .seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }\n\n  \u002F* ---- The demo ---- *\u002F\n  .demo { border: 1px dashed var(--line); border-radius: 12px; padding: 14px; background: #0a0c12; }\n  .layout { display: flex; gap: 0; }\n  .sidebar {\n    flex: 0 0 34%; background: var(--card); border-radius: 10px; padding: 12px;\n    font-size: .82rem; color: var(--muted); line-height: 1.5;\n  }\n  .content {\n    flex: 1; background: var(--card); border-radius: 10px; padding: 12px 14px;\n  }\n  .content h3 { margin: 0 0 6px; font-size: .96rem; }\n  .content p { margin: 0; font-size: .84rem; color: var(--muted); line-height: 1.55; }\n  .badge-new { display: inline-block; font-size: .68rem; font-weight: 800; letter-spacing: .04em;\n    color: #0b0e14; background: var(--good); border-radius: 999px; padding: 2px 8px; margin-bottom: 8px; }\n\n  \u002F* PHYSICAL model — hardcoded to the left\u002Fright edges *\u002F\n  .physical .sidebar { margin-right: 14px; }\n  .physical .content { border-left: 4px solid var(--accent); padding-left: 16px; }\n  .physical .content h3, .physical .content p { text-align: left; }\n\n  \u002F* LOGICAL model — follows the writing direction *\u002F\n  .logical .sidebar { margin-inline-end: 14px; }\n  .logical .content { border-inline-start: 4px solid var(--accent); padding-inline-start: 16px; }\n  .logical .content h3, .logical .content p { text-align: start; }\n\n  \u002F* ---- Verdict + CSS readout ---- *\u002F\n  .verdict { margin: 12px 0 0; padding: 11px 13px; border-radius: 11px; font-size: .86rem; line-height: 1.55; }\n  .verdict.good { color: #9fe6b0; background: #3fb95014; border: 1px solid #3fb95055; }\n  .verdict.bad  { color: #ffcda6; background: #f0883e14; border: 1px solid #f0883e55; }\n  .verdict code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    background: #ffffff12; padding: 1px 5px; border-radius: 5px; font-size: .86em; }\n\n  .code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem;\n    background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px;\n    line-height: 1.65; overflow-x: auto; }\n  .code .sel { color: var(--accent); }\n  .code .prop { color: #86e29b; }\n  .code .cmt { color: #6b7688; }\n  .code .warn { color: var(--bad); }\n\n  @media (max-width: 560px) {\n    .layout { flex-direction: column; gap: 10px; }\n    .physical .sidebar, .logical .sidebar { margin: 0; }\n  }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  let dir = \"ltr\";\n  let model = \"physical\";\n\n  const RTL_TEXT = {\n    hd: \"الخصائص المنطقية\",\n    bd: \"تحتوي هذه البطاقة على حدٍّ ملوّن عند حافة بداية القراءة، ومسافة قبل الشريط الجانبي، ونصٍّ محاذًى إلى حيث تبدأ القراءة.\",\n  };\n  const LTR_TEXT = {\n    hd: \"Logical properties\",\n    bd: \"This card has an accent border on the reading-start edge, a gap before the sidebar, and text aligned to where reading begins. Watch which edge each one clings to.\",\n  };\n\n  const CODE = {\n    physical: `\u003Cspan class=\"sel\">.content\u003C\u002Fspan> {\n  \u003Cspan class=\"prop\">border-left: 4px solid\u003C\u002Fspan> var(--accent);\n  \u003Cspan class=\"prop\">padding-left: 16px;\u003C\u002Fspan>\n  \u003Cspan class=\"prop\">text-align: left;\u003C\u002Fspan>\n}\n\u003Cspan class=\"sel\">.sidebar\u003C\u002Fspan> { \u003Cspan class=\"prop\">margin-right: 14px;\u003C\u002Fspan> }\n\n\u003Cspan class=\"cmt\">\u002F* …and to support RTL you'd ALSO need: *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"sel\">[dir=\"rtl\"] .content\u003C\u002Fspan> {\n  \u003Cspan class=\"warn\">border-left: none;\u003C\u002Fspan>\n  \u003Cspan class=\"warn\">border-right: 4px solid var(--accent);\u003C\u002Fspan>\n  \u003Cspan class=\"warn\">padding-left: 0; padding-right: 16px;\u003C\u002Fspan>\n  \u003Cspan class=\"warn\">text-align: right;\u003C\u002Fspan>\n}`,\n    logical: `\u003Cspan class=\"sel\">.content\u003C\u002Fspan> {\n  \u003Cspan class=\"prop\">border-inline-start: 4px solid\u003C\u002Fspan> var(--accent);\n  \u003Cspan class=\"prop\">padding-inline-start: 16px;\u003C\u002Fspan>\n  \u003Cspan class=\"prop\">text-align: start;\u003C\u002Fspan>\n}\n\u003Cspan class=\"sel\">.sidebar\u003C\u002Fspan> { \u003Cspan class=\"prop\">margin-inline-end: 14px;\u003C\u002Fspan> }\n\n\u003Cspan class=\"cmt\">\u002F* RTL override block? None needed. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"cmt\">\u002F* The browser mirrors it from dir=\"rtl\". *\u002F\u003C\u002Fspan>`,\n  };\n\n  function update() {\n    const demo = $(\"demo\");\n    demo.dir = dir;\n    demo.classList.toggle(\"physical\", model === \"physical\");\n    demo.classList.toggle(\"logical\", model === \"logical\");\n\n    const txt = dir === \"rtl\" ? RTL_TEXT : LTR_TEXT;\n    $(\"hd\").textContent = txt.hd;\n    $(\"bd\").textContent = txt.bd;\n\n    \u002F\u002F Reflect pressed state\n    document.querySelectorAll(\"#dir button\").forEach((b) =>\n      b.setAttribute(\"aria-pressed\", String(b.dataset.dir === dir)));\n    document.querySelectorAll(\"#model button\").forEach((b) =>\n      b.setAttribute(\"aria-pressed\", String(b.dataset.model === model)));\n\n    $(\"code\").innerHTML = CODE[model];\n\n    const v = $(\"verdict\");\n    const broken = dir === \"rtl\" && model === \"physical\";\n    const rtl = dir === \"rtl\";\n    if (broken) {\n      v.className = \"verdict bad\";\n      v.innerHTML =\n        \"⚠️ \u003Cstrong>Broken.\u003C\u002Fstrong> The text now reads right-to-left, but \u003Ccode>border-left\u003C\u002Fcode>, \" +\n        \"\u003Ccode>padding-left\u003C\u002Fcode> and \u003Ccode>text-align: left\u003C\u002Fcode> are still pinned to the physical left. \" +\n        \"The accent edge and the alignment are on the wrong side. You'd patch it with a \u003Ccode>[dir=\\\"rtl\\\"]\u003C\u002Fcode> mirror block.\";\n    } else if (rtl && model === \"logical\") {\n      v.className = \"verdict good\";\n      v.innerHTML =\n        \"✓ \u003Cstrong>Correct.\u003C\u002Fstrong> \u003Ccode>border-inline-start\u003C\u002Fcode>, \u003Ccode>padding-inline-start\u003C\u002Fcode> and \" +\n        \"\u003Ccode>text-align: start\u003C\u002Fcode> followed the reading direction and mirrored to the right — \" +\n        \"\u003Cstrong>with zero extra CSS\u003C\u002Fstrong>. Same rules that worked in LTR.\";\n    } else if (model === \"logical\") {\n      v.className = \"verdict good\";\n      v.innerHTML =\n        \"✓ Looks right in LTR — and because these are \u003Cstrong>logical\u003C\u002Fstrong> properties, the exact same rules \" +\n        \"will mirror correctly the moment you switch to RTL. Try it.\";\n    } else {\n      v.className = \"verdict good\";\n      v.innerHTML =\n        \"In LTR, physical and logical look identical — \u003Ccode>left\u003C\u002Fcode> \u003Cem>is\u003C\u002Fem> the reading start here. \" +\n        \"Now switch the direction to \u003Cstrong>RTL\u003C\u002Fstrong> and see which model survives.\";\n    }\n  }\n\n  $(\"dir\").addEventListener(\"click\", (e) => {\n    const b = e.target.closest(\"button\"); if (!b) return;\n    dir = b.dataset.dir; update();\n  });\n  $(\"model\").addEventListener(\"click\", (e) => {\n    const b = e.target.closest(\"button\"); if (!b) return;\n    model = b.dataset.model; update();\n  });\n\n  update();",{"height":40,"autorun":17},520,{"slug":33,"title":42,"excerpt":43,"coverUrl":44},"Your `margin-left` doesn't exist in Arabic. CSS logical properties fix that.","Physical CSS properties like `margin-left` and `padding-right` break silently when a layout needs to support right-to-left languages. Logical properties replace the physical axis with a writing-mode-aware model — one codebase, every direction.","\u002Fmedia\u002Fcovers\u002Fcss-logical-properties.png"]