[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-css-subgrid-nested-grid-alignment":32},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"slug":33,"title":34,"summary":35,"html":36,"css":37,"js":38,"config":39,"doc":41},"css-subgrid-nested-grid-alignment","subgrid — snap the card footers onto one line",null,"\u003Cdiv class=\"wrap\">\n    \u003Ch1>🎮 subgrid: snap the footers onto one line\u003C\u002Fh1>\n    \u003Cp class=\"sub\">\n      Three cards, one row. Each has a header, a \u003Cstrong>different-length\u003C\u002Fstrong> body, and a footer button.\n      With \u003Ccode>subgrid\u003C\u002Fcode> off, every card is its own grid — the footer sits right under its own body, so the\n      buttons scatter. Flip it on and the cards borrow the parent's \u003Ccode>auto&nbsp;1fr&nbsp;auto\u003C\u002Fcode> rows:\n      headers share a row, footers share a row. \u003Cstrong>No fixed heights, no JavaScript.\u003C\u002Fstrong>\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>1 · Controls\u003C\u002Fh2>\n      \u003Cdiv class=\"controls\">\n        \u003Clabel class=\"toggle\">\n          \u003Cinput type=\"checkbox\" id=\"sub\" \u002F>\n          \u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan class=\"lab\">\u003Ccode>grid-template-rows: subgrid\u003C\u002Fcode>\u003C\u002Fspan>\n        \u003C\u002Flabel>\n        \u003Clabel class=\"toggle\">\n          \u003Cinput type=\"checkbox\" id=\"guide\" checked \u002F>\n          \u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan class=\"lab\">Show footer baseline\u003C\u002Fspan>\n        \u003C\u002Flabel>\n        \u003Cdiv class=\"spacer\">\u003C\u002Fdiv>\n        \u003Cbutton id=\"shuffle\">🎲 Shuffle content lengths\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>2 · The layout\u003C\u002Fh2>\n      \u003Cdiv class=\"stage\">\n        \u003Cdiv class=\"cards\" id=\"cards\">\u003C\u002Fdiv>\n        \u003Cdiv class=\"guide\" id=\"guideLine\">\u003Cspan class=\"tag\" id=\"guideTag\">footers\u003C\u002Fspan>\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"status bad\" id=\"status\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>3 · The CSS that changed\u003C\u002Fh2>\n      \u003Cdiv class=\"code\" id=\"code\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>",":root {\n    --bg: #0d1117; --panel: #161b22; --ink: #e8ebf2; --muted: #9aa4b2;\n    --accent: #7c9cff; --good: #3fb950; --bad: #f0883e; --line: #232a35;\n    --card: #0b0e14; --hd: #1b2130;\n    font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n  }\n  * { box-sizing: border-box; }\n  body { margin: 0; background: var(--bg); color: var(--ink); min-height: 100vh;\n    display: flex; align-items: flex-start; justify-content: center; padding: 18px; }\n  .wrap { width: 100%; max-width: 820px; }\n\n  h1 { font-size: 1.2rem; margin: 0 0 3px; letter-spacing: -0.01em; }\n  .sub { color: var(--muted); font-size: .9rem; margin: 0 0 14px; line-height: 1.45; }\n  .sub code, .sub strong { color: var(--accent); }\n  .sub code { background: #ffffff10; padding: 1px 5px; border-radius: 5px; font-size: .85em;\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n  .sub strong { font-weight: 700; }\n\n  .panel { background: var(--panel); border: 1px solid var(--line);\n    border-radius: 14px; padding: 16px; margin-bottom: 14px; }\n  .panel h2 { font-size: .72rem; text-transform: uppercase; letter-spacing: .07em;\n    color: var(--muted); margin: 0 0 12px; font-weight: 700; }\n\n  \u002F* ---- Controls ---- *\u002F\n  .controls { display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: center; }\n  .toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n    font-size: .9rem; font-weight: 600; user-select: none; }\n  .switch { position: relative; width: 46px; height: 26px; border-radius: 999px;\n    background: #2a3140; border: 1px solid var(--line); transition: background .18s ease; flex: none; }\n  .switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;\n    border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; }\n  input[type=checkbox] { position: absolute; opacity: 0; pointer-events: none; }\n  .toggle input:checked + .switch { background: var(--good); }\n  .toggle input:checked + .switch::after { transform: translateX(20px); background: #fff; }\n  .toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n  .toggle .lab code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    font-size: .82em; background: #ffffff10; padding: 1px 5px; border-radius: 5px; }\n\n  button { font: inherit; font-weight: 600; border-radius: 10px; cursor: pointer;\n    border: 1px solid var(--line); background: transparent; color: var(--ink); padding: 8px 12px; }\n  button:hover { background: #ffffff0a; }\n  button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n  .spacer { flex: 1 1 auto; }\n\n  \u002F* ---- The demo grid ---- *\u002F\n  .stage { position: relative; }\n  .cards {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 14px;\n    align-items: start; \u002F* each card only as tall as its content when NOT subgrid *\u002F\n  }\n  \u002F* subgrid ON: parent defines the shared row tracks and cards inherit them *\u002F\n  .cards.on {\n    grid-template-rows: auto 1fr auto;\n    align-items: stretch;\n  }\n  .card {\n    background: var(--card); border: 1px solid var(--line); border-radius: 12px;\n    overflow: hidden; display: flex; flex-direction: column;\n  }\n  .cards.on .card {\n    display: grid;\n    grid-row: span 3;\n    grid-template-rows: subgrid;\n  }\n  .card-header { background: var(--hd); padding: 10px 12px; font-weight: 700; font-size: .92rem;\n    display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--line); }\n  .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }\n  .card-body { padding: 11px 12px; color: var(--muted); font-size: .84rem; line-height: 1.5; }\n  .card-footer { padding: 11px 12px; border-top: 1px dashed var(--line); }\n  .card-footer button { width: 100%; background: var(--accent); color: #0b0e14; border: none;\n    font-weight: 700; font-size: .84rem; }\n  .card-footer button:hover { filter: brightness(1.07); }\n\n  \u002F* dashed alignment guide under the footers *\u002F\n  .guide { position: absolute; left: -6px; right: -6px; border-top: 2px dashed var(--bad);\n    pointer-events: none; opacity: 0; transition: opacity .18s ease; }\n  .guide.show { opacity: .9; }\n  .guide .tag { position: absolute; right: 0; top: -18px; font-size: .68rem; color: var(--bad);\n    font-weight: 700; background: var(--bg); padding: 0 6px; }\n  .cards.on ~ .guide .tag { color: var(--good); }\n  .cards.on ~ .guide { border-top-color: var(--good); }\n\n  \u002F* ---- CSS readout + status ---- *\u002F\n  .code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem;\n    background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px;\n    line-height: 1.6; overflow-x: auto; }\n  .code .sel { color: var(--accent); }\n  .code .prop { color: #86e29b; }\n  .code .off { color: #6b7688; }\n  .code .hl { background: #3fb95022; border-radius: 4px; padding: 0 3px; }\n\n  .status { font-size: .86rem; line-height: 1.55; margin: 12px 0 0; padding: 11px 13px;\n    border-radius: 11px; }\n  .status.bad { color: #ffcda6; background: #f0883e14; border: 1px solid #f0883e55; }\n  .status.good { color: #9fe6b0; background: #3fb95014; border: 1px solid #3fb95055; }\n  .status code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    background: #ffffff12; padding: 1px 5px; border-radius: 5px; font-size: .86em; }\n\n  @media (max-width: 620px) {\n    .cards, .cards.on { grid-template-columns: 1fr; grid-template-rows: none; }\n    .cards.on .card { grid-row: auto; grid-template-rows: none; display: flex; }\n    .guide { display: none; }\n  }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  const HEADERS = [\n    { t: \"Starter\",   c: \"#3fb950\" },\n    { t: \"Pro\",       c: \"#7c9cff\" },\n    { t: \"Enterprise\",c: \"#f0883e\" },\n  ];\n  const BODIES = [\n    \"Everything to get going.\",\n    \"For growing teams that need collaboration, history, and priority support around the clock.\",\n    \"Advanced controls, SSO, audit logs, a dedicated success manager, and a custom SLA tailored to your org's scale and compliance needs.\",\n  ];\n  const POOL = [\n    \"One line of copy.\",\n    \"A couple of sentences that make this card taller than its neighbour.\",\n    \"For growing teams that need collaboration, shared history, and priority support.\",\n    \"Advanced controls, SSO, audit logs, and a dedicated success manager with a custom SLA.\",\n    \"Short.\",\n    \"Just enough words here to push the footer down a comfortable notch or two.\",\n  ];\n  let bodies = BODIES.slice();\n\n  function renderCards() {\n    const cards = $(\"cards\");\n    cards.innerHTML = HEADERS.map((h, i) => `\n      \u003Cdiv class=\"card\">\n        \u003Cdiv class=\"card-header\">\u003Cspan class=\"dot\" style=\"background:${h.c}\">\u003C\u002Fspan>${h.t}\u003C\u002Fdiv>\n        \u003Cdiv class=\"card-body\">${bodies[i]}\u003C\u002Fdiv>\n        \u003Cdiv class=\"card-footer\">\u003Cbutton>Choose ${h.t}\u003C\u002Fbutton>\u003C\u002Fdiv>\n      \u003C\u002Fdiv>`).join(\"\");\n    positionGuide();\n  }\n\n  \u002F\u002F Draw the dashed baseline at the top of the FIRST card's footer, so it is\n  \u002F\u002F obvious when the other footers do (subgrid) or don't (no subgrid) meet it.\n  function positionGuide() {\n    const line = $(\"guideLine\");\n    if (!$(\"guide\").checked) { line.classList.remove(\"show\"); return; }\n    const footers = document.querySelectorAll(\".card-footer\");\n    if (!footers.length) return;\n    const stage = line.parentElement.getBoundingClientRect();\n    let top = Infinity;\n    footers.forEach((f) => { top = Math.min(top, f.getBoundingClientRect().top); });\n    line.style.top = (top - stage.top) + \"px\";\n    line.classList.add(\"show\");\n  }\n\n  const CODE = {\n    off: `\u003Cspan class=\"sel\">.cards\u003C\u002Fspan> {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 14px;\n}\n\u003Cspan class=\"sel\">.card\u003C\u002Fspan> {\n  \u003Cspan class=\"off\">\u002F* its own box — footer sits under its own body *\u002F\u003C\u002Fspan>\n  display: flex;\n  flex-direction: column;\n}`,\n    on: `\u003Cspan class=\"sel\">.cards\u003C\u002Fspan> {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  \u003Cspan class=\"prop hl\">grid-template-rows: auto 1fr auto;\u003C\u002Fspan>\n  gap: 14px;\n}\n\u003Cspan class=\"sel\">.card\u003C\u002Fspan> {\n  display: grid;\n  \u003Cspan class=\"prop hl\">grid-row: span 3;\u003C\u002Fspan>\n  \u003Cspan class=\"prop hl\">grid-template-rows: subgrid;\u003C\u002Fspan>\n}`,\n  };\n\n  function update() {\n    const on = $(\"sub\").checked;\n    $(\"cards\").classList.toggle(\"on\", on);\n    $(\"code\").innerHTML = on ? CODE.on : CODE.off;\n    const s = $(\"status\");\n    if (on) {\n      s.className = \"status good\";\n      s.innerHTML =\n        \"✓ The cards dropped their own row tracks and adopted the parent's \" +\n        \"\u003Ccode>auto 1fr auto\u003C\u002Fcode>. Every footer lives in the shared bottom track — \" +\n        \"\u003Cstrong>all three buttons sit on one line\u003C\u002Fstrong>, no matter how long the body is.\";\n    } else {\n      s.className = \"status bad\";\n      s.innerHTML =\n        \"⚠️ Each card is its own grid, so its footer sits right below \u003Cem>its own\u003C\u002Fem> body. \" +\n        \"Because the bodies differ in length, \u003Cstrong>the footers land at different heights\u003C\u002Fstrong>. \" +\n        \"This is the moment you'd reach for fixed heights or a JS measurement loop.\";\n    }\n    requestAnimationFrame(positionGuide);\n  }\n\n  $(\"sub\").addEventListener(\"change\", update);\n  $(\"guide\").addEventListener(\"change\", () => requestAnimationFrame(positionGuide));\n  $(\"shuffle\").addEventListener(\"click\", () => {\n    bodies = HEADERS.map(() => POOL[Math.floor(Math.random() * POOL.length)]);\n    renderCards();\n    requestAnimationFrame(positionGuide);\n  });\n  window.addEventListener(\"resize\", () => requestAnimationFrame(positionGuide));\n\n  renderCards();\n  update();",{"height":40,"autorun":17},520,{"slug":33,"title":42,"excerpt":43,"coverUrl":44},"Your CSS Grid cards look misaligned. `subgrid` is the fix you didn't know existed.","CSS Grid controls your outer container, but its children can't participate in the same tracks. `subgrid` closes that gap — nested elements align to the parent grid's rows and columns without padding hacks, fixed heights, or JavaScript measurements.","\u002Fmedia\u002Fcovers\u002Fcss-subgrid-nested-grid-alignment.png"]