{"id":"l-css-2-3","courseId":"css-mastery","lang":"en","kind":"lesson","summary":"Use subgrid so the time, title, abstract and room of every session card line up across a row of cards, whatever the length of each title.","html":"<p>Put three Waypoint session cards side by side and the problem jumps out. One title fits on a line, one wraps to two, one to three. Each card lays out its own content, so the abstracts start at three different heights and the room labels at the bottom form a staircase. Designers notice. Users notice less, but the page looks unfinished.</p>\n<p>The old fixes were fixed heights (which break with real content) or JavaScript that measures every title. Subgrid makes the cards share one set of rows.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Why nested grids don’t line up</h2>\n<p>When a card becomes <code dir=\"ltr\">display: grid</code>, it creates its own, independent tracks. Its title row is as tall as <em>its</em> title; the neighbouring card’s title row is as tall as <em>that</em> title. Nothing connects them, because each card only knows about its own content.</p>\n<p>What you want is for the “title row” to be one track shared across all cards in the same visual row, sized by the tallest title. That shared track has to live in the parent, the <code dir=\"ltr\">.sessions</code> grid, and the cards need to borrow it.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Borrowing the parent’s rows</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">sessions.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\">.sessions</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-fill</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\">min</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\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</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\">24</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>\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\">  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-row</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: span </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;              </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* time, title, abstract, room */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  grid-template-rows</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">subgrid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* use the parent's four rows */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  row-gap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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 style=\"color:#6E7781;--shiki-dark:#8B949E\">/* tighter than the parent's 24px */</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Two declarations do the work. <code dir=\"ltr\">grid-row: span 4</code> makes each card occupy four rows of the parent grid. <code dir=\"ltr\">grid-template-rows: subgrid</code> tells the card not to create its own rows but to use the four it spans. The card’s four children (time, title, abstract, room) are placed into those parent rows in order.</p>\n<p>Now the browser sizes each parent row by the tallest content placed in it <em>from every card in that row</em>. The title track grows to fit the three-line title, the shorter titles get the same track, and every abstract starts at the same line. Room labels line up at the bottom.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 260\" role=\"img\" aria-labelledby=\"l-css-2-3-t1\"><defs><marker id=\"arrow-l-css-2-3-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-3-t1\">Left: three cards whose title blocks have different heights, so the room labels below sit at different heights. Right: the same three cards with shared row lines, so titles share one tall row and the room labels align.</title>\n  <text class=\"d-label-strong\" x=\"20\" y=\"22\">Own rows</text>\n  <rect class=\"d-box\" x=\"20\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"124\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"228\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <rect class=\"d-box-primary\" x=\"28\" y=\"44\" width=\"80\" height=\"24\" rx=\"4\"/>\n  <rect class=\"d-box-primary\" x=\"132\" y=\"44\" width=\"80\" height=\"48\" rx=\"4\"/>\n  <rect class=\"d-box-primary\" x=\"236\" y=\"44\" width=\"80\" height=\"72\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"28\" y=\"76\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"132\" y=\"100\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"236\" y=\"124\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <text class=\"d-label-muted\" x=\"40\" y=\"252\">rooms misaligned</text>\n  <text class=\"d-label-strong\" x=\"380\" y=\"22\">Subgrid rows</text>\n  <rect class=\"d-box\" x=\"380\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"484\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"588\" y=\"34\" width=\"96\" height=\"200\" rx=\"8\"/>\n  <path class=\"d-line d-dashed\" d=\"M372 118 L692 118\"/>\n  <path class=\"d-line d-dashed\" d=\"M372 152 L692 152\"/>\n  <rect class=\"d-box-primary\" x=\"388\" y=\"44\" width=\"80\" height=\"24\" rx=\"4\"/>\n  <rect class=\"d-box-primary\" x=\"492\" y=\"44\" width=\"80\" height=\"48\" rx=\"4\"/>\n  <rect class=\"d-box-primary\" x=\"596\" y=\"44\" width=\"80\" height=\"72\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"388\" y=\"124\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"492\" y=\"124\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <rect class=\"d-box-accent\" x=\"596\" y=\"124\" width=\"80\" height=\"20\" rx=\"4\"/>\n  <text class=\"d-label-muted\" x=\"420\" y=\"252\">rows shared, aligned</text>\n</svg><figcaption>Without subgrid each card sizes its own rows; with subgrid they share the parent’s rows</figcaption></figure>\n<p>Notice that the parent never declares <code dir=\"ltr\">grid-template-rows</code>. It doesn’t need to: the cards’ spans create implicit rows, and those rows are sized from the content the subgrids place in them. Every new row of cards gets its own four tracks, so cards in the second visual row align with each other but not with the first row, which is what you want.</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>Forgetting the span</span></p>\n<p><code dir=\"ltr\">grid-template-rows: subgrid</code> on a card that spans one parent row gives the subgrid one track to share. A subgrid can’t create extra rows in the subgridded direction, so all four children are placed into that single row and pile on top of each other. The span must match the number of parts. If cards vary in parts (some have no abstract), keep the slots and leave the empty ones empty, or give the missing part an explicit row with <code dir=\"ltr\">grid-row</code>.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Gaps, lines and columns</h2>\n<p>A subgrid inherits the parent’s <code dir=\"ltr\">gap</code>. Here the parent uses 24px between cards, which is far too much between a title and its abstract, so the card overrides <code dir=\"ltr\">row-gap</code> for its own tracks. The space <em>between</em> cards is unaffected.</p>\n<p>Subgrid works on columns too. A classic case on Waypoint is the speaker profile form: labels in one column, inputs in the next, and every label column the same width across several fieldsets. Making each fieldset <code dir=\"ltr\">grid-column: span 2; grid-template-columns: subgrid</code> gives every fieldset the outer form’s columns, so labels align all the way down. Named grid lines from the parent are also visible inside the subgrid, so <code dir=\"ltr\">grid-column: content-start / content-end</code> keeps working one level down.</p>\n<aside class=\"callout callout-why\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>Before subgrid, card alignment was a design request front-end developers had to push back on or fake. Subgrid has been Baseline since September 2023 (Chrome 117, Firefox 71, Safari 16) and widely available since March 2026, so you can say yes. In an old browser the cards fall back to normal stacked grids: still readable, just not aligned.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Your turn</h2>\n<p>The exercise gives you three session cards with titles of different lengths. Turn each card into a row subgrid so that abstracts and room labels line up across the row. The checks compare the vertical positions of those parts across cards. The next lesson stays with sizing: <code dir=\"ltr\">min()</code>, <code dir=\"ltr\">max()</code>, <code dir=\"ltr\">clamp()</code> and the keywords that let content decide how big things are.</p>\n","toc":[{"id":"sec-1","text":"Why nested grids don’t line up","level":2},{"id":"sec-2","text":"Borrowing the parent’s rows","level":2},{"id":"sec-3","text":"Gaps, lines and columns","level":2},{"id":"sec-4","text":"Your turn","level":2}],"takeaways":["A nested grid with <code dir=\"ltr\">grid-template-rows: subgrid</code> uses its parent’s row tracks instead of creating its own, so content in sibling cards shares the same rows.","The subgridded item must span as many parent tracks as it has parts, for example <code dir=\"ltr\">grid-row: span 4</code> for a four-part card.","A subgrid inherits the parent’s gap by default and can override it with its own <code dir=\"ltr\">row-gap</code> or <code dir=\"ltr\">column-gap</code>.","Subgrid has been Baseline since September 2023 and widely available since March 2026, so aligned card layouts no longer need fixed heights or JavaScript measuring."],"further":[{"title":"Subgrid (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Subgrid"},{"title":"CSS subgrid (web.dev)","url":"https://web.dev/articles/css-subgrid"}],"quiz":[{"q":"<p>Each card has four children (time, title, abstract, room). Why don’t the rooms line up?</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\">.sessions</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><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 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\">3</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\">.session-card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">grid-template-rows</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">subgrid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }</span></span></code></pre></div>\n","options":[{"text":"The card only spans one parent row, so the subgrid has a single track to share.","why":"Correct. A subgrid adopts only the parent tracks it spans, and it can’t add implicit rows of its own. With one track, all four children land in that same row and overlap."},{"text":"<code dir=\"ltr\">subgrid</code> only works on columns, not rows.","why":"<code dir=\"ltr\">subgrid</code> works on either axis or both: <code dir=\"ltr\">grid-template-rows: subgrid</code>, <code dir=\"ltr\">grid-template-columns: subgrid</code>."},{"text":"The parent needs <code dir=\"ltr\">grid-template-rows: subgrid</code> too.","why":"The parent is the grid that owns the tracks; only the nested grid uses the <code dir=\"ltr\">subgrid</code> keyword."}],"answer":0},{"q":"<p>What’s the most robust way to make every card’s room label align across a row when titles wrap to different numbers of lines?</p>","options":[{"text":"Give every title a fixed <code dir=\"ltr\">height: 3em</code>.","why":"A fourth line of title overflows, and a one-line title leaves a hole; fixed heights break the moment content changes."},{"text":"Measure the tallest title in JavaScript and set the others to match.","why":"It works, but it’s slow, runs again on every resize and font load, and is exactly the job subgrid now does in CSS."},{"text":"Make each card a row subgrid spanning four parent rows.","why":"Correct. All cards in a row share the same four tracks, so each track grows to the tallest content and everything below lines up."}],"answer":2},{"q":"<p>The parent grid has <code dir=\"ltr\">gap: 24px</code>, and the subgridded cards look too spread out inside. What do you change?</p>","options":[{"text":"Set <code dir=\"ltr\">gap: 0</code> on the parent grid.","why":"That also removes the space between the cards themselves, which you wanted to keep."},{"text":"Set <code dir=\"ltr\">row-gap: 8px</code> on the subgridded card.","why":"Correct. A subgrid inherits the parent’s gaps by default but can override them for its own tracks."},{"text":"Add negative margins to the card’s children.","why":"Negative margins fight the layout instead of configuring it, and break when the gap changes."},{"text":"Nothing; the inherited gap can’t be changed in a subgrid.","why":"The gap can be overridden; only the track sizes come from the parent."}],"answer":1}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<section class=\"sessions\">\n  <article class=\"session-card\">\n    <p class=\"time\">09:30</p>\n    <h3>Subgrid</h3>\n    <p class=\"abstract\">Aligning card content across a row without fixed heights.</p>\n    <p class=\"room\">Room A</p>\n  </article>\n  <article class=\"session-card\">\n    <p class=\"time\">10:15</p>\n    <h3>Container Queries in a Design System With Forty Components</h3>\n    <p class=\"abstract\">What changed when every component started measuring itself.</p>\n    <p class=\"room\">Room B</p>\n  </article>\n  <article class=\"session-card\">\n    <p class=\"time\">11:00</p>\n    <h3>Color Spaces for Humans Who Ship Interfaces Every Day of the Week</h3>\n    <p class=\"abstract\">oklch(), color-mix() and palettes that stay accessible.</p>\n    <p class=\"room\">Main hall</p>\n  </article>\n</section>","css":".sessions {\n  width: 660px; /* fixed for this exercise */\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 20px;\n}\n.session-card {\n  padding: 12px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card > * { margin: 0; }\n.time { color: #6b7280; font-size: 0.85rem; }\nh3 { font-size: 1rem; }\n.room { font-weight: 600; color: #6d28d9; }\n\n/* Make each card a row subgrid here */\n"},"solution":{"html":"<section class=\"sessions\">\n  <article class=\"session-card\">\n    <p class=\"time\">09:30</p>\n    <h3>Subgrid</h3>\n    <p class=\"abstract\">Aligning card content across a row without fixed heights.</p>\n    <p class=\"room\">Room A</p>\n  </article>\n  <article class=\"session-card\">\n    <p class=\"time\">10:15</p>\n    <h3>Container Queries in a Design System With Forty Components</h3>\n    <p class=\"abstract\">What changed when every component started measuring itself.</p>\n    <p class=\"room\">Room B</p>\n  </article>\n  <article class=\"session-card\">\n    <p class=\"time\">11:00</p>\n    <h3>Color Spaces for Humans Who Ship Interfaces Every Day of the Week</h3>\n    <p class=\"abstract\">oklch(), color-mix() and palettes that stay accessible.</p>\n    <p class=\"room\">Main hall</p>\n  </article>\n</section>","css":".sessions {\n  width: 660px; /* fixed for this exercise */\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 20px;\n}\n.session-card {\n  padding: 12px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card > * { margin: 0; }\n.time { color: #6b7280; font-size: 0.85rem; }\nh3 { font-size: 1rem; }\n.room { font-weight: 600; color: #6d28d9; }\n\n.session-card {\n  display: grid;\n  grid-row: span 4;\n  grid-template-rows: subgrid;\n  row-gap: 8px;\n}\n"},"checks":[{"id":"three-across","test":"return new Set($$('.session-card').map((el) => Math.round(el.getBoundingClientRect().top))).size === 1;"},{"id":"uses-subgrid","test":"return $$('.session-card').every((el) => style(el, 'grid-template-rows').startsWith('subgrid'));"},{"id":"abstracts-align","test":"const tops = (sel) => $$(sel).map((el) => el.getBoundingClientRect().top); const same = (arr) => Math.max(...arr) - Math.min(...arr) < 1; return same(tops('.abstract'));"},{"id":"rooms-align","test":"const tops = (sel) => $$(sel).map((el) => el.getBoundingClientRect().top); const same = (arr) => Math.max(...arr) - Math.min(...arr) < 1; return same(tops('.room'));"},{"id":"no-overlap","test":"return $$('.session-card').every((card) => { const kids = [...card.children].map((c) => c.getBoundingClientRect()); return kids.every((r, i) => i === 0 || r.top >= kids[i - 1].bottom - 0.5); });"}],"title":"Line up the session cards","prompt":"<p>The three cards sit in a three-column grid, but their titles wrap to different numbers of lines, so the abstracts and room labels sit at different heights. Make each <code dir=\"ltr\">.session-card</code> a <strong>row subgrid</strong> that spans the four parts (time, title, abstract, room), so that every abstract starts at the same height and every room label lines up. Keep the cards side by side and don’t use fixed heights.</p>\n","hints":["The card needs to occupy four rows of the parent grid before it can borrow them.","Two declarations on the card do most of the work: one span and one <code dir=\"ltr\">subgrid</code>. The card also needs <code dir=\"ltr\">display: grid</code>.","<code dir=\"ltr\">.session-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; }</code> (add <code dir=\"ltr\">row-gap</code> if the spacing looks too wide)."],"explanation":"<p><code dir=\"ltr\">grid-row: span 4</code> makes each card occupy four rows of the <code dir=\"ltr\">.sessions</code> grid, and <code dir=\"ltr\">grid-template-rows: subgrid</code> makes the card use those rows instead of creating its own. Each parent row is now sized by the tallest item placed in it from all three cards, so the title row fits the longest title and every abstract and room label starts on the same line. Without the span, the card has one track to share, so its children would all be placed in that one row and overlap.</p>\n","checkLabels":{"three-across":"The cards are still side by side","uses-subgrid":"Each card uses grid-template-rows: subgrid","abstracts-align":"All abstracts start at the same height","rooms-align":"All room labels line up","no-overlap":"Nothing inside a card overlaps"}},"words":699,"readMinutes":3}