{"id":"l-css-2-2","courseId":"css-mastery","lang":"en","kind":"lesson","summary":"Lay out a page with named grid-template-areas and build a speaker grid that picks its own column count with repeat(auto-fit, minmax()).","html":"<p>Flexbox lays things out in one direction and lets each line make its own decisions. That’s perfect for a row of filters and wrong for Waypoint’s speaker grid, where every card in a column should line up with the cards above and below it. When you need rows <em>and</em> columns to agree, you want grid.</p>\n<p>This lesson builds two layouts on the schedule page: the page shell, with named areas, and the speaker grid, which decides its own number of columns.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Draw the page with template areas</h2>\n<p>The schedule page has a header across the top, a filter sidebar and the main content. With <code dir=\"ltr\">grid-template-areas</code> you draw that layout directly:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">layout.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\">.page</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\">grid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-columns</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">14</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> minmax</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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\">fr</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-areas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"header  header\"</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"filters main\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  gap</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:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.site-header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">grid-area</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: header; }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.filters</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">     { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">grid-area</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: filters; }</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\">grid-area</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: main; }</span></span></code></pre></div>\n<p>Each string is a row; each word inside it is a column. Repeating a name (<code dir=\"ltr\">header header</code>) makes that area span both columns. A <code dir=\"ltr\">.</code> marks an empty cell. Areas must be rectangles: an L-shape is invalid and the whole declaration is dropped.</p>\n<p>The pay-off comes when the layout changes. On narrow screens the filters should sit above the sessions, so you redraw the strings instead of moving markup:</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:#CF222E;--shiki-dark:#FF7B72\">@media</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">width</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 48</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:#0550AE;--shiki-dark:#79C0FF\">  .page</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    grid-template-columns</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">minmax</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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\">fr</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    grid-template-areas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      \"header\"</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      \"filters\"</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      \"main\"</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:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\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>Reordering visually, not in the DOM</span></p>\n<p>Grid areas can place elements in any visual order, but keyboard focus and screen readers follow the DOM order. If you put the filters visually <em>after</em> the sessions but they come first in the HTML, a keyboard user tabs through invisible-feeling jumps. Keep the source order logical and use areas to adjust layout, not meaning.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">The <code dir=\"ltr\">fr</code> unit and its hidden minimum</h2>\n<p><code dir=\"ltr\">fr</code> divides the free space in the grid after fixed tracks and gaps are subtracted. Two <code dir=\"ltr\">1fr</code> columns in a 600px grid with a 20px gap are 290px each.</p>\n<p>There’s a catch you’ll meet sooner or later: <code dir=\"ltr\">1fr</code> is shorthand for <code dir=\"ltr\">minmax(auto, 1fr)</code>, and <code dir=\"ltr\">auto</code> as a minimum means “at least the content’s min-content width”. Put a long unbroken URL or a code block in one column and it widens, stealing space from the other. That’s why the page shell above uses <code dir=\"ltr\">minmax(0, 1fr)</code>: it lets the main column shrink below its content, and any overflow is handled inside it (with <code dir=\"ltr\">overflow-x: auto</code> on the code block, for instance). It’s the grid cousin of the <code dir=\"ltr\">min-width: 0</code> fix from the previous lesson.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">A speaker grid that counts its own columns</h2>\n<p>Waypoint has 23 speakers. On a phone they should stack; on a laptop, four or five across. One declaration handles every width:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">speakers.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\">.speakers</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\">grid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-columns</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">repeat</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">auto-fit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">minmax</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">180</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</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\">fr</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  gap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</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\">.speaker.featured</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-column</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</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>Read it inside out. <code dir=\"ltr\">minmax(180px, 1fr)</code> says each column is at least 180px and at most an equal share of the free space. <code dir=\"ltr\">repeat(auto-fit, …)</code> asks the browser to create as many such columns as fit. In a 640px container with 16px gaps, three 180px columns fit (three columns plus two gaps is 572px) and four don’t, so you get three columns, each stretched to about 203px.</p>\n<p><code dir=\"ltr\">grid-column: 1 / -1</code> makes the keynote speaker span from the first line to the last, whatever the column count. Negative line numbers count from the end of the explicit grid.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-css-2-2-t1\"><defs><marker id=\"arrow-l-css-2-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-2-2-t1\">Two grids of the same width with two items each. With auto-fill, the items occupy two of four equal columns and two empty dashed tracks remain. With auto-fit, the empty tracks collapse and the two items each take half the width.</title>\n  <text class=\"d-code\" x=\"20\" y=\"24\">auto-fill</text>\n  <rect class=\"d-box\" x=\"20\" y=\"36\" width=\"640\" height=\"64\" rx=\"8\"/>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"46\" width=\"146\" height=\"44\" rx=\"6\"/>\n  <rect class=\"d-box-primary\" x=\"186\" y=\"46\" width=\"146\" height=\"44\" rx=\"6\"/>\n  <rect class=\"d-box d-dashed\" x=\"342\" y=\"46\" width=\"146\" height=\"44\" rx=\"6\"/>\n  <rect class=\"d-box d-dashed\" x=\"498\" y=\"46\" width=\"152\" height=\"44\" rx=\"6\"/>\n  <text class=\"d-label-muted\" x=\"380\" y=\"74\">empty</text>\n  <text class=\"d-label-muted\" x=\"540\" y=\"74\">empty</text>\n  <text class=\"d-code\" x=\"20\" y=\"138\">auto-fit</text>\n  <rect class=\"d-box\" x=\"20\" y=\"150\" width=\"640\" height=\"64\" rx=\"8\"/>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"160\" width=\"306\" height=\"44\" rx=\"6\"/>\n  <rect class=\"d-box-primary\" x=\"344\" y=\"160\" width=\"306\" height=\"44\" rx=\"6\"/>\n</svg><figcaption>auto-fill keeps empty tracks; auto-fit collapses them so items stretch</figcaption></figure>\n<p>The difference between <code dir=\"ltr\">auto-fit</code> and <code dir=\"ltr\">auto-fill</code> only shows when there are fewer items than columns. <code dir=\"ltr\">auto-fill</code> keeps the empty tracks, so two speakers stay card-sized and leave a gap. <code dir=\"ltr\">auto-fit</code> collapses empty tracks to zero, so the two speakers stretch across the row. For a speaker grid that’s sometimes nearly empty (day two has only four speakers), pick based on the design: <code dir=\"ltr\">auto-fill</code> for consistent card widths, <code dir=\"ltr\">auto-fit</code> for a full row.</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>Guard the minimum on tiny screens</span></p>\n<p>If the container is narrower than 180px, <code dir=\"ltr\">minmax(180px, 1fr)</code> overflows. Writing <code dir=\"ltr\">minmax(min(180px, 100%), 1fr)</code> caps the minimum at the container’s width. You’ll see why that works in <a href=\"/lesson/css-mastery/l-css-2-4\">Intrinsic Sizing</a>.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Your turn</h2>\n<p>The exercise gives you the page shell and the speaker grid with hard-coded columns. Name the areas, switch the speakers to <code dir=\"ltr\">auto-fit</code>, and make the keynote span the whole row. The checks resize the page to make sure the column count adapts. Next, you’ll make the <em>contents</em> of grid cards line up across cards with subgrid.</p>\n","toc":[{"id":"sec-1","text":"Draw the page with template areas","level":2},{"id":"sec-2","text":"The fr unit and its hidden minimum","level":2},{"id":"sec-3","text":"A speaker grid that counts its own columns","level":2},{"id":"sec-4","text":"Your turn","level":2}],"takeaways":["<code dir=\"ltr\">grid-template-areas</code> lets you draw the layout as ASCII art, and rearranging the page for another screen size means redrawing the strings, not moving markup.","<code dir=\"ltr\">repeat(auto-fit, minmax(12rem, 1fr))</code> creates as many columns as fit and stretches them to fill the row, with no media queries.","<code dir=\"ltr\">auto-fill</code> keeps empty tracks when there are few items, while <code dir=\"ltr\">auto-fit</code> collapses them so the items stretch to fill the row.","<code dir=\"ltr\">1fr</code> has an automatic minimum of the content size; use <code dir=\"ltr\">minmax(0, 1fr)</code> when a track must be allowed to shrink below its content."],"further":[{"title":"Grid template areas (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Grid_template_areas"},{"title":"repeat() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/repeat"},{"title":"Grid (web.dev Learn CSS)","url":"https://web.dev/learn/css/grid"}],"quiz":[{"q":"<p>A speaker grid uses <code dir=\"ltr\">repeat(auto-fill, minmax(180px, 1fr))</code> in a 900px container but only has two speakers. What do you see?</p>","options":[{"text":"Two wide cards that share the full 900px.","why":"That’s <code dir=\"ltr\">auto-fit</code>, which collapses empty tracks. <code dir=\"ltr\">auto-fill</code> keeps them."},{"text":"Two cards about 180px wide, with the empty tracks still taking up the rest of the row.","why":"Correct. <code dir=\"ltr\">auto-fill</code> creates as many tracks as fit (five here) whether or not there are items for them, so two cards sit in the first two of five equal columns."},{"text":"Two cards exactly 180px wide, left-aligned, with no extra tracks.","why":"The cards are about that wide, but not because extra tracks are missing. Three empty tracks exist and hold the space; that’s exactly what keeps the two cards narrow."}],"answer":1},{"q":"<p>What does this layout put in the second row, first column?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><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\">.page</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\">grid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-columns</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">14</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">fr</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-areas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"header header\"</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"filters main\"</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","options":[{"text":"The element with <code dir=\"ltr\">grid-area: header</code>.","why":"The header area covers both columns of the first row only."},{"text":"Nothing; areas must be rectangular, so this is invalid.","why":"Both areas here are rectangles (header is 1×2, filters and main are 1×1), so the template is valid."},{"text":"The element with <code dir=\"ltr\">grid-area: filters</code>.","why":"Correct. Each string is a row and each word a column, so the second row’s first column is <code dir=\"ltr\">filters</code>."}],"answer":2},{"q":"<p>A grid with <code dir=\"ltr\">grid-template-columns: 1fr 1fr</code> gets a code block with a very long line in its first column, and the columns become unequal. What’s the fix?</p>","options":[{"text":"<code dir=\"ltr\">grid-template-columns: minmax(0, 1fr) minmax(0, 1fr)</code>","why":"Correct. A bare <code dir=\"ltr\">1fr</code> has an automatic minimum equal to the content’s min-content size; <code dir=\"ltr\">minmax(0, 1fr)</code> removes that floor so both columns stay equal."},{"text":"<code dir=\"ltr\">grid-template-columns: 50% 50%</code>","why":"Percentages ignore the gap, so with any <code dir=\"ltr\">gap</code> the grid overflows its container."},{"text":"<code dir=\"ltr\">grid-auto-flow: dense</code>","why":"<code dir=\"ltr\">dense</code> changes how items are placed into holes; it doesn’t affect track sizing."},{"text":"<code dir=\"ltr\">justify-items: stretch</code>","why":"That’s already the default and only affects how items fill their cells, not how wide the tracks are."}],"answer":0}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<div class=\"page\">\n  <header class=\"site-header\">Waypoint Conf 2026</header>\n  <nav class=\"filters\">Tracks</nav>\n  <main class=\"speakers\">\n    <div class=\"speaker featured\">Keynote · Ana Ortiz</div>\n    <div class=\"speaker\">Kenji Watanabe</div>\n    <div class=\"speaker\">Layla Haddad</div>\n    <div class=\"speaker\">Tom Becker</div>\n    <div class=\"speaker\">Priya Nair</div>\n    <div class=\"speaker\">Marcus Lee</div>\n  </main>\n</div>","css":".page {\n  width: 860px; /* fixed for this exercise */\n  display: grid;\n  gap: 20px;\n}\n.site-header, .filters, .speaker {\n  padding: 12px;\n  border-radius: 10px;\n  background: #f5f3ff;\n}\n.page {\n  grid-template-columns: 200px minmax(0, 1fr);\n}\n\n.speakers {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 16px;\n}\n"},"solution":{"html":"<div class=\"page\">\n  <header class=\"site-header\">Waypoint Conf 2026</header>\n  <nav class=\"filters\">Tracks</nav>\n  <main class=\"speakers\">\n    <div class=\"speaker featured\">Keynote · Ana Ortiz</div>\n    <div class=\"speaker\">Kenji Watanabe</div>\n    <div class=\"speaker\">Layla Haddad</div>\n    <div class=\"speaker\">Tom Becker</div>\n    <div class=\"speaker\">Priya Nair</div>\n    <div class=\"speaker\">Marcus Lee</div>\n  </main>\n</div>","css":".page {\n  width: 860px; /* fixed for this exercise */\n  display: grid;\n  gap: 20px;\n}\n.site-header, .filters, .speaker {\n  padding: 12px;\n  border-radius: 10px;\n  background: #f5f3ff;\n}\n.page {\n  grid-template-columns: 200px minmax(0, 1fr);\n  grid-template-areas:\n    \"header  header\"\n    \"filters main\";\n}\n.site-header { grid-area: header; }\n.filters { grid-area: filters; }\n.speakers { grid-area: main; }\n\n.speakers {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n  gap: 16px;\n}\n.speaker.featured {\n  grid-column: 1 / -1;\n}\n"},"checks":[{"id":"uses-areas","test":"return style('.page', 'grid-template-areas') !== 'none';"},{"id":"header-spans","test":"const box = (s) => (typeof s === 'string' ? $(s) : s).getBoundingClientRect(); const cols = () => new Set($$('.speaker:not(.featured)').filter((el) => style(el, 'display') !== 'none').map((el) => Math.round(box(el).left))).size; const h = box('.site-header'), f = box('.filters'), m = box('.speakers'); return Math.abs(h.width - 860) < 1 && f.top >= h.bottom && m.top >= h.bottom && f.right <= m.left;"},{"id":"three-columns","test":"const box = (s) => (typeof s === 'string' ? $(s) : s).getBoundingClientRect(); const cols = () => new Set($$('.speaker:not(.featured)').filter((el) => style(el, 'display') !== 'none').map((el) => Math.round(box(el).left))).size; return cols() === 3;"},{"id":"featured-spans","test":"const box = (s) => (typeof s === 'string' ? $(s) : s).getBoundingClientRect(); const cols = () => new Set($$('.speaker:not(.featured)').filter((el) => style(el, 'display') !== 'none').map((el) => Math.round(box(el).left))).size; return Math.abs(box('.speaker.featured').width - box('.speakers').width) < 1;"},{"id":"narrow-two-columns","test":"const box = (s) => (typeof s === 'string' ? $(s) : s).getBoundingClientRect(); const cols = () => new Set($$('.speaker:not(.featured)').filter((el) => style(el, 'display') !== 'none').map((el) => Math.round(box(el).left))).size; const page = $('.page'); page.style.width = '620px'; await tick(16); const n = cols(); page.style.width = ''; await tick(16); return n === 2;"},{"id":"few-items-stretch","test":"const box = (s) => (typeof s === 'string' ? $(s) : s).getBoundingClientRect(); const cols = () => new Set($$('.speaker:not(.featured)').filter((el) => style(el, 'display') !== 'none').map((el) => Math.round(box(el).left))).size; const hide = $$('.speaker').slice(0, 4); hide.forEach((el) => (el.style.display = 'none')); await tick(16); const w = box($$('.speaker')[4]).width; const main = box('.speakers').width; hide.forEach((el) => (el.style.display = '')); await tick(16); return Math.abs(w - (main - 16) / 2) < 1.5;"}],"title":"Name the areas, let the speakers flow","prompt":"<p>The page is fixed at 860px for this exercise.</p>\n<ol>\n<li>Give <code dir=\"ltr\">.page</code> a <code dir=\"ltr\">grid-template-areas</code> layout: <code dir=\"ltr\">header</code> across both columns on the first row, then <code dir=\"ltr\">filters</code> and <code dir=\"ltr\">main</code> side by side. Assign <code dir=\"ltr\">.site-header</code>, <code dir=\"ltr\">.filters</code> and <code dir=\"ltr\">.speakers</code> to those areas.</li>\n<li>Replace the speakers’ fixed <code dir=\"ltr\">repeat(3, 1fr)</code> with columns that are <strong>at least 180px</strong>, as many as fit, and that <strong>stretch to fill the row</strong> when there are only a few speakers.</li>\n<li>Make the <code dir=\"ltr\">.featured</code> speaker span the full row.</li>\n</ol>\n<p>The checks resize the page and hide some speakers to see how your grid reacts.</p>\n","hints":["Each string in <code dir=\"ltr\">grid-template-areas</code> is one row; repeat a name to span columns.","For the speakers you want <code dir=\"ltr\">repeat()</code> with a keyword that collapses empty tracks, and <code dir=\"ltr\">minmax()</code> with 180px as the minimum.","<code dir=\"ltr\">grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));</code> and <code dir=\"ltr\">.speaker.featured { grid-column: 1 / -1; }</code>."],"explanation":"<p><code dir=\"ltr\">grid-template-areas</code> describes the layout as rows of names; <code dir=\"ltr\">header header</code> spans both columns and <code dir=\"ltr\">filters main</code> puts the two side by side, and <code dir=\"ltr\">grid-area</code> drops each element into its slot. For the speakers, <code dir=\"ltr\">minmax(180px, 1fr)</code> sets each column’s range and <code dir=\"ltr\">repeat(auto-fit, …)</code> creates as many as fit: three in the 640px main column, two when the page shrinks. With only two speakers visible, <code dir=\"ltr\">auto-fit</code> collapses the empty tracks so the cards stretch to half the row each; <code dir=\"ltr\">auto-fill</code> would have left them narrow. <code dir=\"ltr\">grid-column: 1 / -1</code> runs the keynote from the first grid line to the last, whatever the column count.</p>\n","checkLabels":{"uses-areas":"The page uses grid-template-areas","header-spans":"The header spans the top; filters sit left of the speakers","three-columns":"The speakers form three columns at this width","featured-spans":"The featured speaker spans the full row","narrow-two-columns":"A narrower page gets two columns","few-items-stretch":"With two speakers, they stretch to fill the row"}},"words":678,"readMinutes":3}