{"id":"l-css-3-2","courseId":"css-mastery","lang":"en","kind":"lesson","summary":"Replace physical properties with logical ones so the schedule page works in right-to-left languages like Arabic, and handle the few things that don’t flip on their own.","html":"<p>Waypoint is adding an Arabic edition of the schedule for its Cairo satellite event. The HTML change is one attribute, <code dir=\"ltr\">&lt;html lang=&quot;ar&quot; dir=&quot;rtl&quot;&gt;</code>, and the first load was half right. The session list mirrored correctly, because it’s a flex row. But every card had its colored border on the <em>left</em>, which in Arabic is the end of the line, the time column had its spacing on the wrong side, and the “Details →” arrow pointed backwards.</p>\n<p>The layout was written in physical directions: left, right, top, bottom. Text doesn’t flow in physical directions; it flows from a start to an end.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Inline and block axes</h2>\n<p>Logical properties name sides by the flow of text:</p>\n<ul>\n<li>The <strong>inline</strong> axis is the direction text runs within a line. In English it’s left to right; in Arabic, right to left.</li>\n<li>The <strong>block</strong> axis is the direction lines stack: top to bottom in both.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-css-3-2-t1\"><defs><marker id=\"arrow-l-css-3-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-css-3-2-t1\">Two cards. In the left-to-right card, inline-start is on the left and inline-end on the right. In the right-to-left card, inline-start is on the right and inline-end on the left. In both, block-start is at the top and block-end at the bottom.</title>\n  <text class=\"d-label-strong\" x=\"40\" y=\"24\">dir=\"ltr\"</text>\n  <rect class=\"d-box\" x=\"40\" y=\"40\" width=\"260\" height=\"140\" rx=\"10\"/>\n  <rect class=\"d-box-primary\" x=\"40\" y=\"40\" width=\"10\" height=\"140\"/>\n  <text class=\"d-code\" x=\"60\" y=\"116\">inline-start</text>\n  <text class=\"d-code\" x=\"196\" y=\"116\">inline-end</text>\n  <text class=\"d-label-muted\" x=\"128\" y=\"62\">block-start</text>\n  <text class=\"d-label-muted\" x=\"132\" y=\"172\">block-end</text>\n  <text class=\"d-label-strong\" x=\"380\" y=\"24\">dir=\"rtl\"</text>\n  <rect class=\"d-box\" x=\"380\" y=\"40\" width=\"260\" height=\"140\" rx=\"10\"/>\n  <rect class=\"d-box-primary\" x=\"630\" y=\"40\" width=\"10\" height=\"140\"/>\n  <text class=\"d-code\" x=\"392\" y=\"116\">inline-end</text>\n  <text class=\"d-code\" x=\"522\" y=\"116\">inline-start</text>\n  <text class=\"d-label-muted\" x=\"468\" y=\"62\">block-start</text>\n  <text class=\"d-label-muted\" x=\"472\" y=\"172\">block-end</text>\n  <text class=\"d-label-muted\" x=\"40\" y=\"214\">border-inline-start sits at the start of the line in both</text>\n</svg><figcaption>The same card in LTR and RTL: inline-start flips sides, block-start stays on top</figcaption></figure>\n<p>Every physical property has a logical twin:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Physical</th>\n<th>Logical</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">margin-left</code>, <code dir=\"ltr\">margin-right</code></td>\n<td><code dir=\"ltr\">margin-inline-start</code>, <code dir=\"ltr\">margin-inline-end</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">padding-top</code>, <code dir=\"ltr\">padding-bottom</code></td>\n<td><code dir=\"ltr\">padding-block-start</code>, <code dir=\"ltr\">padding-block-end</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">border-left</code></td>\n<td><code dir=\"ltr\">border-inline-start</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">left</code>, <code dir=\"ltr\">right</code>, <code dir=\"ltr\">top</code></td>\n<td><code dir=\"ltr\">inset-inline-start</code>, <code dir=\"ltr\">inset-inline-end</code>, <code dir=\"ltr\">inset-block-start</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">width</code>, <code dir=\"ltr\">height</code></td>\n<td><code dir=\"ltr\">inline-size</code>, <code dir=\"ltr\">block-size</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">text-align: left</code></td>\n<td><code dir=\"ltr\">text-align: start</code></td>\n</tr>\n</tbody>\n</table></div><p>There are shorthands too: <code dir=\"ltr\">margin-inline: 1rem 2rem</code> sets start and end, <code dir=\"ltr\">padding-block: 1rem</code> sets both block sides, and <code dir=\"ltr\">inset-inline: 0</code> replaces <code dir=\"ltr\">left: 0; right: 0</code>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Converting the session card</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">session-card.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* Before: physical */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  border-left</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> solid</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--track-color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 8</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 12</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 20</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  text-align</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">left</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> .time</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">margin-right</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* After: logical */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  border-inline-start</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> solid</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--track-color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding-block</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding-inline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 8</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  text-align</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">start</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> .time</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">margin-inline-end</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span></code></pre></div>\n<p>In English the result is pixel-identical. In Arabic, the border moves to the right, where lines start, and the spacing follows. You write the card once.</p>\n<p>Flexbox and grid already think this way. <code dir=\"ltr\">flex-direction: row</code>, <code dir=\"ltr\">justify-content: flex-start</code> and grid column 1 all follow the inline direction, so a flex row mirrors in RTL without any extra CSS. That’s why Waypoint’s list worked on the first try.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Converting margins but not positions</span></p>\n<p>Teams convert <code dir=\"ltr\">margin</code> and <code dir=\"ltr\">padding</code> and forget absolutely positioned elements. A “Live now” badge at <code dir=\"ltr\">right: 8px</code> stays on the right in Arabic, on top of the title text. Search the codebase for <code dir=\"ltr\">left:</code> and <code dir=\"ltr\">right:</code> too, and replace them with <code dir=\"ltr\">inset-inline-start</code> and <code dir=\"ltr\">inset-inline-end</code>.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Sizes are logical too</h2>\n<p><code dir=\"ltr\">inline-size</code> and <code dir=\"ltr\">block-size</code> are the logical names for width and height, and <code dir=\"ltr\">max-inline-size: 40ch</code> is the logical version of the readable-width cap from the intrinsic sizing lesson. In Arabic and English they behave exactly like <code dir=\"ltr\">width</code> and <code dir=\"ltr\">height</code>, because both scripts write horizontally. They only differ in vertical writing modes, such as <code dir=\"ltr\">writing-mode: vertical-rl</code> for Japanese or a rotated table header, where the inline axis runs top to bottom. Using them costs nothing today and means a component survives a writing-mode change later. A sensible team rule is: logical properties for everything that relates to text flow, physical ones only for things that are physical by nature, like a shadow that should always fall downward.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">What doesn’t flip by itself</h2>\n<p>A few things are physical by nature and need an explicit RTL rule:</p>\n<ul>\n<li><strong>Transforms</strong>: <code dir=\"ltr\">translateX(8px)</code> always moves right.</li>\n<li><strong>Directional icons</strong>: arrows, “next” chevrons, a progress indicator’s direction.</li>\n<li><strong>Background positions and shadows</strong>: <code dir=\"ltr\">box-shadow: 4px 0 …</code> always throws its shadow to the right.</li>\n</ul>\n<p>The <code dir=\"ltr\">:dir()</code> pseudo-class matches the element’s computed direction, inherited from the nearest <code dir=\"ltr\">dir</code> attribute:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">CSS</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.more</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> .arrow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  display</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">inline-block</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  margin-inline-start</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.more</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> .arrow:dir</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">rtl</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  scale</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">-1</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* mirror horizontally */</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">:dir(rtl)</code> is better than <code dir=\"ltr\">[dir=&quot;rtl&quot;] .arrow</code> because it doesn’t care where the attribute lives or whether a nested element switches back to LTR. It has been Baseline since December 2023 and widely available since mid-2026.</p>\n<p>Not every icon should flip. A play button, a clock, or a checkmark means the same in both directions; a “back” arrow or a slider’s direction doesn’t. Ask a native reader when you’re unsure.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Arabic typography in two rules</h2>\n<p>First, <strong>never apply <code dir=\"ltr\">letter-spacing</code> to Arabic</strong>. Arabic letters join to their neighbours; spacing them apart breaks the joins and makes words hard to read. If your design system adds tracking to uppercase labels, reset it with <code dir=\"ltr\">:lang(ar) { letter-spacing: 0; }</code>. Second, Arabic often needs a slightly larger font size and line height than Latin text at the same visual weight. Set those per language with <code dir=\"ltr\">:lang(ar)</code> rather than per component.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Mixed-direction content</span></p>\n<p>User-generated content (a speaker’s bio, a session title typed in English on the Arabic page) can be in either direction. <code dir=\"ltr\">dir=&quot;auto&quot;</code> on the element lets the browser pick the direction from the first strong character, and <code dir=\"ltr\">&lt;bdi&gt;</code> isolates a name inside a sentence so punctuation doesn’t jump to the wrong side.</p>\n</aside>\n<h2 id=\"sec-6\" tabindex=\"-1\">Your turn</h2>\n<p>The exercise card is written with physical properties. Convert it to logical ones and flip the arrow in RTL. The checks render it in both directions. Next, you’ll replace the card’s hex colors with <code dir=\"ltr\">oklch()</code> and build track palettes with <code dir=\"ltr\">color-mix()</code>.</p>\n","toc":[{"id":"sec-1","text":"Inline and block axes","level":2},{"id":"sec-2","text":"Converting the session card","level":2},{"id":"sec-3","text":"Sizes are logical too","level":2},{"id":"sec-4","text":"What doesn’t flip by itself","level":2},{"id":"sec-5","text":"Arabic typography in two rules","level":2},{"id":"sec-6","text":"Your turn","level":2}],"takeaways":["Logical properties describe layout relative to the text direction: <code dir=\"ltr\">margin-inline-start</code> is the left margin in English and the right margin in Arabic.","Setting <code dir=\"ltr\">dir=&quot;rtl&quot;</code> on the root mirrors flexbox, grid and logical properties automatically; physical properties like <code dir=\"ltr\">padding-left</code> stay where they are.","Transforms, background positions, shadows and directional icons don’t flip, so handle them explicitly with <code dir=\"ltr\">:dir(rtl)</code>.","Never add <code dir=\"ltr\">letter-spacing</code> to Arabic text; it breaks the joins between letters."],"further":[{"title":"Basic concepts of logical properties and values (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values/Basic_concepts"},{"title":"Logical properties (web.dev Learn CSS)","url":"https://web.dev/learn/css/logical-properties"},{"title":"Structural markup and right-to-left text in HTML (W3C)","url":"https://www.w3.org/International/questions/qa-html-dir"}],"quiz":[{"q":"<p>A card has <code dir=\"ltr\">padding-left: 20px; border-left: 6px solid</code>. You set <code dir=\"ltr\">dir=&quot;rtl&quot;</code> on <code dir=\"ltr\">&lt;html&gt;</code>. Where are the padding and border now?</p>","options":[{"text":"On the right, because the browser mirrors the whole layout.","why":"The browser mirrors direction-aware layout (flex, grid, logical properties), but <code dir=\"ltr\">left</code> always means the physical left."},{"text":"Still on the left, which is now the end of each line.","why":"Correct. Physical properties ignore direction. Use <code dir=\"ltr\">padding-inline-start</code> and <code dir=\"ltr\">border-inline-start</code> to follow the text."},{"text":"Removed, because physical properties are ignored in RTL.","why":"They still apply; they just apply to the physical side you named."}],"answer":1},{"q":"<p>In a flex row with <code dir=\"ltr\">justify-content: flex-start</code> inside a <code dir=\"ltr\">dir=&quot;rtl&quot;</code> page, where do the items start?</p>","options":[{"text":"On the left, because flex-start is the left edge.","why":"Flexbox’s start follows the inline direction, so in RTL the start is the right edge."},{"text":"In the center, because flexbox can’t decide.","why":"Flexbox always resolves start and end from the writing direction; it never falls back to centering."},{"text":"It depends on whether <code dir=\"ltr\">flex-direction: row-reverse</code> is set.","why":"row-reverse would swap them again, but with the default <code dir=\"ltr\">row</code> the answer is already determined."},{"text":"On the right, because flex-start follows the inline direction.","why":"Correct. Flexbox and grid are direction-aware out of the box; that’s why most of a flex layout mirrors for free."}],"answer":3},{"q":"<p>A “Details →” link has an arrow icon that should point the other way in Arabic. Which rule does it?</p>","options":[{"text":"<code dir=\"ltr\">.arrow:dir(rtl) { scale: -1 1; }</code>","why":"Correct. <code dir=\"ltr\">:dir(rtl)</code> matches elements whose computed direction is RTL, and a negative horizontal scale mirrors the icon."},{"text":"<code dir=\"ltr\">.arrow { margin-inline-start: 4px; }</code>","why":"That moves the spacing to the correct side, which is good, but doesn’t change which way the arrow points."},{"text":"<code dir=\"ltr\">.arrow { direction: rtl; }</code>","why":"<code dir=\"ltr\">direction</code> changes text and layout direction, not how a glyph or icon is drawn."}],"answer":0},{"q":"<p>Which declaration sets a session card’s width in a way that also works for vertical writing modes?</p>","options":[{"text":"<code dir=\"ltr\">width: 20rem</code>","why":"<code dir=\"ltr\">width</code> is always horizontal; in a vertical writing mode the inline axis runs top to bottom."},{"text":"<code dir=\"ltr\">block-size: 20rem</code>","why":"Block size is the height in horizontal writing, the dimension across lines, not along them."},{"text":"<code dir=\"ltr\">inline-size: 20rem</code>","why":"Correct. <code dir=\"ltr\">inline-size</code> is the size along the direction text runs, so it’s the width in English and Arabic and the height in vertical text."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<div class=\"wrap\" dir=\"ltr\">\n  <article class=\"session-card\">\n    <span class=\"time\">09:30</span>\n    <h3>Logical Properties for Real Products</h3>\n    <a class=\"more\" href=\"#details\">Details <span class=\"arrow\" aria-hidden=\"true\">→</span></a>\n  </article>\n</div>","css":".wrap { width: 360px; }\n.session-card {\n  padding-top: 12px;\n  padding-bottom: 12px;\n  border-radius: 12px;\n  background: #f8fafc;\n}\n.session-card h3 { margin: 8px 0; }\n\n.session-card {\n  border-left: 6px solid #6d28d9;\n  padding-left: 20px;\n  padding-right: 8px;\n  text-align: left;\n}\n.time {\n  display: inline-block;\n  margin-right: 12px;\n  color: #6b7280;\n}\n.more {\n  display: block;\n  text-align: right;\n}\n.arrow {\n  display: inline-block;\n  margin-left: 4px;\n}\n"},"solution":{"html":"<div class=\"wrap\" dir=\"ltr\">\n  <article class=\"session-card\">\n    <span class=\"time\">09:30</span>\n    <h3>Logical Properties for Real Products</h3>\n    <a class=\"more\" href=\"#details\">Details <span class=\"arrow\" aria-hidden=\"true\">→</span></a>\n  </article>\n</div>","css":".wrap { width: 360px; }\n.session-card {\n  padding-top: 12px;\n  padding-bottom: 12px;\n  border-radius: 12px;\n  background: #f8fafc;\n}\n.session-card h3 { margin: 8px 0; }\n\n.session-card {\n  border-inline-start: 6px solid #6d28d9;\n  padding-inline: 20px 8px;\n  text-align: start;\n}\n.time {\n  display: inline-block;\n  margin-inline-end: 12px;\n  color: #6b7280;\n}\n.more {\n  display: block;\n  text-align: end;\n}\n.arrow {\n  display: inline-block;\n  margin-inline-start: 4px;\n}\n.arrow:dir(rtl) {\n  scale: -1 1;\n}\n"},"checks":[{"id":"ltr-unchanged","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('ltr'); return px('.session-card', 'border-left-width') === 6 && px('.session-card', 'border-right-width') === 0 && px('.session-card', 'padding-left') === 20 && px('.time', 'margin-right') === 12 && !flipped();"},{"id":"rtl-border","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('rtl'); let ok = false; try { ok = (px('.session-card', 'border-right-width') === 6 && px('.session-card', 'border-left-width') === 0 && style('.session-card', 'border-right-color') === 'rgb(109, 40, 217)'); } finally { await setDir('ltr'); } return ok;"},{"id":"rtl-padding","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('rtl'); let ok = false; try { ok = (px('.session-card', 'padding-right') === 20 && px('.session-card', 'padding-left') === 8); } finally { await setDir('ltr'); } return ok;"},{"id":"rtl-time-gap","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('rtl'); let ok = false; try { ok = (px('.time', 'margin-left') === 12 && px('.time', 'margin-right') === 0); } finally { await setDir('ltr'); } return ok;"},{"id":"rtl-link-aligned","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('rtl'); let ok = false; try { ok = (['end', 'left'].includes(style('.more', 'text-align')) && px('.arrow', 'margin-right') === 4); } finally { await setDir('ltr'); } return ok;"},{"id":"rtl-arrow-flips","test":"const setDir = async (d) => { $('.wrap').setAttribute('dir', d); await tick(16); }; const px = (sel, p) => parseFloat(style(sel, p)); const flipped = () => style('.arrow', 'scale') === '-1 1' || style('.arrow', 'transform').startsWith('matrix(-1'); await setDir('rtl'); let ok = false; try { ok = (flipped()); } finally { await setDir('ltr'); } return ok;"}],"title":"Make the card work right to left","prompt":"<p>The card is written with physical properties, so it breaks when the wrapper switches to <code dir=\"ltr\">dir=&quot;rtl&quot;</code>. Without changing the HTML:</p>\n<ul>\n<li>Replace the left border, the left/right padding, the time’s right margin, the <code dir=\"ltr\">text-align</code> values and the arrow’s left margin with <strong>logical</strong> equivalents, so English looks exactly the same.</li>\n<li>In RTL, mirror the arrow icon horizontally (use <code dir=\"ltr\">:dir(rtl)</code>).</li>\n</ul>\n<p>The checks render the card in both directions.</p>\n","hints":["<code dir=\"ltr\">border-left</code> becomes <code dir=\"ltr\">border-inline-start</code>; <code dir=\"ltr\">margin-right</code> becomes <code dir=\"ltr\">margin-inline-end</code>.","<code dir=\"ltr\">text-align: left</code> becomes <code dir=\"ltr\">start</code> and <code dir=\"ltr\">right</code> becomes <code dir=\"ltr\">end</code>. <code dir=\"ltr\">padding-inline</code> takes a start and an end value.","For the icon: <code dir=\"ltr\">.arrow:dir(rtl) { scale: -1 1; }</code> (or <code dir=\"ltr\">transform: scaleX(-1)</code>)."],"explanation":"<p>Logical properties resolve against the element’s direction, so <code dir=\"ltr\">border-inline-start</code> is the left border in English and the right border in Arabic, and the same goes for <code dir=\"ltr\">padding-inline</code>, <code dir=\"ltr\">margin-inline-end</code> and <code dir=\"ltr\">text-align: start/end</code>. The English rendering is unchanged, and switching <code dir=\"ltr\">dir</code> mirrors the card with no extra rules. Directional icons are the exception: a glyph doesn’t know which way it should point, so <code dir=\"ltr\">:dir(rtl)</code> matches the arrow when its inherited direction is right to left and <code dir=\"ltr\">scale: -1 1</code> mirrors it.</p>\n","checkLabels":{"ltr-unchanged":"In English, the card looks exactly as before","rtl-border":"In RTL, the colored border is on the right","rtl-padding":"In RTL, the padding follows the text direction","rtl-time-gap":"In RTL, the space after the time is on its left","rtl-link-aligned":"In RTL, the link aligns to the end and the arrow’s gap flips","rtl-arrow-flips":"In RTL, the arrow is mirrored"}},"words":768,"readMinutes":4}