{"id":"l-css-3-1","courseId":"css-mastery","lang":"en","kind":"lesson","summary":"Build fluid type and spacing with clamp(), calculate the slope and intercept from two design sizes, and keep text resizable for users who change their font size.","html":"<p>Waypoint’s hero heading, “Two days of front-end, in Lisbon”, is 48px on the design for a laptop and 28px on the design for a phone. The first implementation had three media queries and the heading jumped between three sizes as you resized. Nobody resizes a window for fun, but they do rotate tablets, split screens and open the page in narrow side panels, and every jump in between looked accidental.</p>\n<p>Fluid type replaces the jumps with a straight line between the two designs.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A fluid value is a line</h2>\n<p>You have two points from the design: at a 20rem (320px) viewport the heading is 1.75rem; at 60rem (960px) it’s 3rem. Between them you want it to grow in proportion; outside them, it should stop. That’s <code dir=\"ltr\">clamp()</code> with a linear preferred value:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">type.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\">.hero</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  font-size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.125</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 3.125</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The middle value is a straight line, <code dir=\"ltr\">intercept + slope × width</code>. The <strong>slope</strong> is how much the size changes per unit of width: (3 − 1.75) ÷ (60 − 20) = 0.03125, which as a viewport unit is 3.125vw. The <strong>intercept</strong> is where that line crosses zero width: 1.75 − 0.03125 × 20 = 1.125rem. The calculation is easy to get wrong by hand, so here it is as a function you can run:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"Run this example\"><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=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><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:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fluid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">minSize</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">maxSize</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">minWidth</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">maxWidth</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">unit</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'vw'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // sizes and widths in rem</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> slope</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (maxSize </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minSize) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (maxWidth </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minWidth);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> intercept</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minSize </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> slope </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minWidth;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> round</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(n </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `clamp(${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">minSize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem, ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">intercept</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem + ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">slope</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unit</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}, ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">maxSize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem)`</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">fluid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">60</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// hero heading</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">fluid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">50</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cqi'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// card title, container-based</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>Try other pairs. Two design sizes in, one <code dir=\"ltr\">clamp()</code> out.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Why the rem matters</h2>\n<p>You could write <code dir=\"ltr\">font-size: 5vw</code> and call it fluid. Don’t. Viewport units know nothing about the user’s font preferences: someone who sets their browser’s default text size to 20px gets exactly the same 5vw as everyone else. WCAG’s Resize Text criterion asks that text can scale to 200% without loss of content, and type that ignores the user’s settings fails people who need it most.</p>\n<p>Including a <code dir=\"ltr\">rem</code> term keeps the user in the equation. With <code dir=\"ltr\">1.125rem + 3.125vw</code>, the rem part grows when the user raises their default size, and the <code dir=\"ltr\">rem</code> limits move with it.</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>Fluid type with pixel limits</span></p>\n<p><code dir=\"ltr\">clamp(18px, 1rem + 1vw, 28px)</code> looks responsible, but the px limits ignore user preferences at both ends. A user with a 24px default is clamped back down to 28px maximum as if they never asked. Write limits in <code dir=\"ltr\">rem</code>. And test it: set your browser’s default font size to 20px and zoom to 200%, and make sure the text still grows.</p>\n</aside>\n<p>There’s a mathematical edge to watch: if the ratio between MAX and MIN is large and the slope is steep, zooming can shrink the effective range so much that 200% zoom doesn’t produce 200% text. Keep the max no more than about 2.5 times the min for body copy and headings, and you’re safe.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Viewport or container?</h2>\n<p><code dir=\"ltr\">vw</code> is right for type tied to the page: the hero heading, the page title. For type inside a component, use container units. A session card’s title in the 280px sidebar and in the 640px main column should differ, and <code dir=\"ltr\">vw</code> can’t tell them apart. With the slot as a size container (from the previous lesson), the same formula works with <code dir=\"ltr\">cqi</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><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\">.slot</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">container-type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: inline-size; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  font-size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">cqi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<h2 id=\"sec-4\" tabindex=\"-1\">Fluid space</h2>\n<p>Type that scales while padding stays fixed looks wrong at both ends: cramped on big screens, airy on small ones. Define a few spacing tokens with the same technique and use them everywhere:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">tokens.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\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --space-s</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --space-m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1.25</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --space-l</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</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>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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\">--space-m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.schedule</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">     { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">gap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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\">--space-l</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span></code></pre></div>\n<p>Three or four steps are enough. More than that and nobody remembers which to use.</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>Two finishing touches for headings</span></p>\n<p><code dir=\"ltr\">text-wrap: balance</code> evens out the line lengths of short multi-line text, so a two-line heading doesn’t end with one orphaned word. It’s Baseline 2024. Use a unitless <code dir=\"ltr\">line-height</code> (like <code dir=\"ltr\">1.15</code> for headings, <code dir=\"ltr\">1.5</code> for body) so it scales with whatever font size <code dir=\"ltr\">clamp()</code> resolves to.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Your turn</h2>\n<p>The exercise’s card title uses <code dir=\"ltr\">5cqi</code>, which grows without limits and ignores the user’s font size. Replace it with a clamp() that hits 16px in a 320px slot and 28px in an 800px slot, with rem limits. The checks change the slot width and even the root font size. Next, you’ll make the same page work in Arabic, right to left, with logical properties.</p>\n","toc":[{"id":"sec-1","text":"A fluid value is a line","level":2},{"id":"sec-2","text":"Why the rem matters","level":2},{"id":"sec-3","text":"Viewport or container?","level":2},{"id":"sec-4","text":"Fluid space","level":2},{"id":"sec-5","text":"Your turn","level":2}],"takeaways":["A fluid value is a straight line between two points: <code dir=\"ltr\">clamp(MIN, intercept + slope × width, MAX)</code>, where the slope is the size change divided by the width change.","Put <code dir=\"ltr\">rem</code> in the intercept and the limits so text still responds to the user’s default font size and to zoom; viewport-only sizes don’t.","Use <code dir=\"ltr\">vw</code> for page-level type such as the hero heading, and <code dir=\"ltr\">cqi</code> for type inside components that live in containers of different widths.","Fluid spacing tokens such as <code dir=\"ltr\">--space-m</code> keep padding and gaps proportional to type without a breakpoint for each."],"further":[{"title":"clamp() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp"},{"title":"Typography (web.dev Learn CSS)","url":"https://web.dev/learn/css/typography"},{"title":"Understanding Success Criterion 1.4.4: Resize Text (W3C)","url":"https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html"}],"quiz":[{"q":"<p>A heading should be 24px when its container is 400px wide and 40px at 1200px, growing linearly in between. Which preferred value does that?</p>","options":[{"text":"<code dir=\"ltr\">16px + 2cqi</code>","why":"Correct. The slope is (40 − 24) / (1200 − 400) = 0.02, so 2cqi; the intercept is 24 − 0.02 × 400 = 16px."},{"text":"<code dir=\"ltr\">3cqi</code>","why":"3cqi is 12px at 400px and 36px at 1200px; with no intercept the line passes through zero, so it can’t hit both targets."},{"text":"<code dir=\"ltr\">24px + 2cqi</code>","why":"That’s 32px at 400px. The intercept is the value where the width is zero, not the size at the smallest width."},{"text":"<code dir=\"ltr\">2px + 2cqi</code>","why":"The slope is right, but 2px + 8px is only 10px at 400px; the intercept must make the line pass through 24px there."}],"answer":0},{"q":"<p>Why is <code dir=\"ltr\">font-size: 4vw</code> on body text an accessibility problem?</p>","options":[{"text":"Screen readers can’t read text sized in viewport units.","why":"Screen readers ignore font size entirely; the problem is visual."},{"text":"Text sized only in <code dir=\"ltr\">vw</code> ignores the user’s default font size, so people who set larger text in their browser don’t get it.","why":"Correct. <code dir=\"ltr\">vw</code> doesn’t involve <code dir=\"ltr\">rem</code> at all; mixing in a <code dir=\"ltr\">rem</code> term (and <code dir=\"ltr\">rem</code> limits) keeps the user’s preference in the calculation."},{"text":"vw units are not supported on mobile browsers.","why":"Viewport units are supported everywhere; the issue is that they don’t respond to font-size preferences."}],"answer":1},{"q":"<p>A session card appears in a 280px sidebar and a 700px main column on the same page. Which unit should its fluid title use?</p>","options":[{"text":"<code dir=\"ltr\">vw</code>, because it’s the standard unit for fluid type.","why":"The viewport is the same for both cards, so <code dir=\"ltr\">vw</code> gives both titles the same size despite very different slots."},{"text":"<code dir=\"ltr\">em</code>, because it scales with the parent font size.","why":"<code dir=\"ltr\">em</code> responds to font size, not to available width, so it can’t make the title fluid."},{"text":"<code dir=\"ltr\">cqi</code>, inside a clamp() with rem limits.","why":"Correct. <code dir=\"ltr\">cqi</code> measures the card’s container, so each card scales to its own slot, and the rem limits keep the result readable and user-adjustable."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<div class=\"slot\">\n  <article class=\"session-card\">\n    <h3>Fluid Type Without Breakpoints</h3>\n    <p>One clamp() instead of three media queries.</p>\n  </article>\n</div>","css":".slot {\n  container-type: inline-size;\n  width: 480px; /* the checks change this width */\n}\n.session-card {\n  padding: 16px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card h3 {\n  margin: 0 0 8px;\n  line-height: 1.2;\n  font-size: 5cqi;\n}\n"},"solution":{"html":"<div class=\"slot\">\n  <article class=\"session-card\">\n    <h3>Fluid Type Without Breakpoints</h3>\n    <p>One clamp() instead of three media queries.</p>\n  </article>\n</div>","css":".slot {\n  container-type: inline-size;\n  width: 480px; /* the checks change this width */\n}\n.session-card {\n  padding: 16px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card h3 {\n  margin: 0 0 8px;\n  line-height: 1.2;\n  font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);\n}\n"},"checks":[{"id":"min-at-320","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(320)) - 16) < 0.3;"},{"id":"max-at-800","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(800)) - 28) < 0.3;"},{"id":"linear-between","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(560)) - 22) < 0.3;"},{"id":"clamped-outside","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; const small = await at(200); const big = await at(1200); return Math.abs(small - 16) < 0.3 && Math.abs(big - 28) < 0.3;"},{"id":"respects-user-size","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; const root = document.documentElement; root.style.fontSize = '20px'; await tick(16); const small = await at(200); const big = await at(1200); root.style.fontSize = ''; await tick(16); return Math.abs(small - 20) < 0.3 && Math.abs(big - 35) < 0.3;"}],"title":"A fluid card title","prompt":"<p>The card title uses <code dir=\"ltr\">font-size: 5cqi</code>, which grows forever and ignores the user’s font size. Replace it with a <code dir=\"ltr\">clamp()</code> so the title is:</p>\n<ul>\n<li><strong>16px (1rem)</strong> when the slot is 320px wide,</li>\n<li><strong>28px (1.75rem)</strong> when the slot is 800px wide,</li>\n<li>linear in between, and clamped outside that range.</li>\n</ul>\n<p>Write the <strong>limits in rem</strong>: the last check sets the root font size to 20px, and the limits must scale with it (to 20px and 35px). The slot is already a size container, so <code dir=\"ltr\">cqi</code> measures it.</p>\n","hints":["Slope = (28 − 16) / (800 − 320) = 0.025, which is 2.5cqi.","Intercept = 16 − 0.025 × 320 = 8px. Write it in rem so it follows the user’s font size.","<code dir=\"ltr\">font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);</code>"],"explanation":"<p>The preferred value is the straight line through the two design points: a slope of 12px over 480px is 0.025 (2.5cqi), and the intercept that puts 16px at 320px is 8px, or 0.5rem. <code dir=\"ltr\">clamp()</code> holds the result between 1rem and 1.75rem. Because the limits and the intercept are in rem, a user who sets a 20px default gets a 20px to 35px range instead of being clamped back to the designer’s sizes; px limits would have failed that last check.</p>\n","checkLabels":{"min-at-320":"16px in a 320px slot","max-at-800":"28px in an 800px slot","linear-between":"22px halfway, at 560px","clamped-outside":"Stays at 16px below and 28px above the range","respects-user-size":"The limits follow the user’s font size (rem, not px)"}},"words":669,"readMinutes":3}