Category: reference
CSS Subgrid — Nested Grids ที่ Align กับ Parent
CSS Subgrid ช่วยให้ nested elements align กับ grid ของ parent ได้ — แก้ปัญหา misaligned columns ใน card grids
สารบัญ
ปัญหาที่ Subgrid แก้
ก่อน Subgrid เวลามี grid ของ cards ที่มีเนื้อหาความยาวต่างกัน elements ข้างในจะไม่ align กัน:
/* ❌ ปัญหา: title และ body ของแต่ละ card ไม่ align ข้าม column */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.card {
display: flex;
flex-direction: column;
/* title ของ card A สูง 2 บรรทัด, card B สูง 1 บรรทัด → body ไม่ตรงกัน */
}
Subgrid คืออะไร
/* ✓ Subgrid: card ใช้ rows จาก parent grid */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto 1fr auto; /* title | meta | body | link */
gap: 1.5rem 1rem;
}
.card {
display: grid;
grid-row: span 4; /* card ใช้ 4 rows */
grid-template-rows: subgrid; /* inherit rows จาก parent */
}
/* ทุก card จะ align กันอัตโนมัติ แม้ content ความยาวต่างกัน */
.card-title { grid-row: 1; } /* row แรกของ parent */
.card-meta { grid-row: 2; }
.card-body { grid-row: 3; }
.card-link { grid-row: 4; align-self: end; }
ตัวอย่างสมบูรณ์: Card Grid
<div class="card-grid">
<article class="card">
<h2 class="card-title">Short Title</h2>
<p class="card-meta">2 min read</p>
<p class="card-body">This card has a very long description that spans multiple lines and pushes the link down further than expected.</p>
<a class="card-link" href="#">Read more</a>
</article>
<article class="card">
<h2 class="card-title">A Much Longer Card Title That Wraps to Two Lines</h2>
<p class="card-meta">5 min read</p>
<p class="card-body">Short description.</p>
<a class="card-link" href="#">Read more</a>
</article>
<article class="card">
<h2 class="card-title">Another Card</h2>
<p class="card-meta">3 min read</p>
<p class="card-body">Medium length description that takes up some space.</p>
<a class="card-link" href="#">Read more</a>
</article>
</div>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-template-rows: auto; /* rows สร้างอัตโนมัติ */
align-items: start;
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 4;
grid-template-rows: subgrid; /* ✓ key: subgrid */
padding: 1.5rem;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
.card-title { font-size: 1.1rem; font-weight: 700; }
.card-meta { font-size: 0.85rem; color: #94a3b8; }
.card-body { color: #334155; line-height: 1.6; }
.card-link {
align-self: end; /* ✓ ดันลงล่างสุด */
color: #2563eb;
font-weight: 600;
text-decoration: none;
}
Subgrid บน Columns ด้วย
/* ใช้ subgrid กับ columns ก็ได้ */
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.item {
grid-column: 1 / -1; /* span ทั้ง row */
display: grid;
grid-template-columns: subgrid; /* ✓ ใช้ columns จาก parent */
}
.item .label { grid-column: 1; } /* column แรก */
.item .content { grid-column: 2; } /* column กลาง */
.item .action { grid-column: 3; } /* column สุดท้าย */
Full-bleed Elements ใน Article Layout
/* Layout ที่ content อยู่กลาง แต่บาง elements ยื่นออก */
.article {
display: grid;
grid-template-columns:
1fr
min(65ch, 100%) /* content width */
1fr;
gap: 0;
}
.article > * {
grid-column: 2; /* ทุก element อยู่ column กลาง */
}
.full-bleed {
grid-column: 1 / -1; /* ยืดเต็มความกว้าง */
}
/* ถ้า .article มี children ที่เป็น wrapper เอง → ใช้ subgrid */
.article .prose {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid; /* ใช้ columns เดียวกับ parent */
}
.article .prose > * { grid-column: 2; }
.article .prose > .full-bleed { grid-column: 1 / -1; }
Named Grid Lines กับ Subgrid
.parent {
display: grid;
grid-template-columns: [sidebar-start] 240px [sidebar-end content-start] 1fr [content-end];
}
.child {
grid-column: sidebar-start / content-end;
display: grid;
grid-template-columns: subgrid;
/* ✓ ได้ named lines จาก parent ด้วย! */
}
.child .nav { grid-column: sidebar-start / sidebar-end; }
.child .main { grid-column: content-start / content-end; }
Browser Support
รองรับ Chrome 117+, Firefox 71+, Safari 16+ — ใช้งานได้ทั่วไปแล้วใน 2024+
/* Progressive enhancement */
.card {
display: flex;
flex-direction: column; /* fallback */
}
@supports (grid-template-rows: subgrid) {
.card-grid {
display: grid;
grid-template-rows: auto;
}
.card {
display: grid;
grid-row: span 4;
grid-template-rows: subgrid;
}
}
เปรียบเทียบกับวิธีเก่า
/* ❌ วิธีเก่า: ใช้ min-height ตายตัว — แตกเมื่อ content ยาว */
.card-title { min-height: 3rem; }
/* ❌ วิธีเก่า: ใช้ JS วัด height — ช้า, janky */
const heights = [...cards].map(c => c.querySelector('.title').offsetHeight);
const max = Math.max(...heights);
cards.forEach(c => c.querySelector('.title').style.minHeight = `${max}px`);
/* ✓ Subgrid: ไม่ต้องทำอะไรเพิ่ม — CSS จัดการเอง */