Category: reference
CSS Nesting — Native Nested Selectors
CSS Nesting ที่รองรับใน browser ตั้งแต่ Chrome 120+ ทำให้เขียน nested selectors ได้โดยไม่ต้องใช้ Sass — ครอบคลุม syntax, &, @nest, และ gotchas
สารบัญ
CSS Nesting คืออะไร
ก่อนหน้านี้ CSS ไม่รองรับ nested selectors — ต้องใช้ Sass/Less หรือเขียนซ้ำทุก selector ตอนนี้ browser รองรับ native แล้ว ไม่ต้องการ preprocessor
/* ❌ ก่อน: ต้องเขียนซ้ำ */
.card { padding: 1rem; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card .card-title { font-size: 1.1rem; }
.card .card-title:hover { color: #2563eb; }
.card .card-body { color: #64748b; }
/* ✓ ตอนนี้: CSS nesting */
.card {
padding: 1rem;
&:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card-title {
font-size: 1.1rem;
&:hover { color: #2563eb; }
}
.card-body { color: #64748b; }
}
& Selector — เชื่อม Parent
.button {
color: white;
background: #2563eb;
/* & แทน .button */
&:hover { background: #1d4ed8; }
&:focus-visible { outline: 2px solid #2563eb; }
&:disabled { opacity: 0.5; cursor: not-allowed; }
/* & ต่อท้าย modifier class */
&.is-loading { cursor: wait; }
&.is-large { font-size: 1.1rem; padding: 0.75rem 1.5rem; }
/* & หลาย selectors */
&:hover, &:focus { text-decoration: underline; }
}
& เพื่อเพิ่ม Specificity หรือ Scope
/* & อยู่หน้า selector = parent ต้องมี context */
.theme-dark {
& .button { background: #1e40af; }
/* → .theme-dark .button */
}
/* & อยู่หลัง = compound selector */
.button {
.is-active & { font-weight: 700; }
/* → .is-active .button */
}
/* Double & */
.button {
&& { specificity เพิ่มขึ้น }
/* → .button.button */
}
At-rules ใน Nested Context
.hero {
font-size: 1rem;
/* @media ใน nested block */
@media (min-width: 768px) {
font-size: 1.25rem;
}
@media (prefers-color-scheme: dark) {
color: #f1f5f9;
}
/* @container query */
@container (min-width: 400px) {
display: grid;
grid-template-columns: 1fr 1fr;
}
/* @supports */
@supports (display: grid) {
display: grid;
}
/* @layer */
@layer utilities {
font-weight: 700;
}
}
Dark Mode Pattern ด้วย Nesting
/* ✓ เก็บ dark mode ไว้ใกล้ rule ที่ light mode */
.card {
background: white;
color: #0f172a;
border: 1px solid rgba(0,0,0,0.1);
[data-theme='dark'] & {
background: rgba(30, 41, 59, 0.8);
color: #f1f5f9;
border-color: rgba(255,255,255,0.08);
}
/* หรือด้วย media query */
@media (prefers-color-scheme: dark) {
background: rgba(30, 41, 59, 0.8);
color: #f1f5f9;
}
}
Component Pattern
.nav {
display: flex;
gap: 1rem;
padding: 0.75rem 1rem;
&-brand {
font-weight: 700;
font-size: 1rem;
}
/* → .nav-brand (ถ้าไม่มี space ก่อน - คือ &-brand)
CAUTION: นี่คือ compound (.nav ที่มี class -brand เชื่อมกัน)
ไม่ใช่ descendent selector */
}
/* ✓ วิธีที่ถูกต้องสำหรับ BEM-style naming */
.nav {
display: flex;
& .nav-brand { font-weight: 700; } /* descendent */
& .nav-link { color: #475569; }
& .nav-link:hover { color: #2563eb; }
}
Gotchas และ ข้อจำกัด
/* ❌ ไม่ได้: nested selector ขึ้นต้นด้วยตัวอักษรหรือตัวเลข ต้องใช้ & */
.parent {
div { color: red; } /* ❌ ไม่ parse ถูกใน Chrome เก่า (120 ก่อน update) */
& div { color: red; } /* ✓ */
}
/* ✓ Chrome 120+ รองรับ implicit & แล้ว */
.parent {
div { color: red; } /* ✓ Chrome 120+, Firefox 117+, Safari 17.2+ */
}
/* ❌ ไม่ได้: pseudo-element ต้องมี & */
.button {
::before { content: ''; } /* ❌ */
&::before { content: ''; } /* ✓ */
}
เปรียบเทียบกับ Sass
/* Sass */
.card {
padding: 1rem;
&:hover { box-shadow: ...; }
&__title { /* BEM element */
font-size: 1.1rem;
}
&--featured { /* BEM modifier */
border-color: #2563eb;
}
}
/* → .card, .card:hover, .card__title, .card--featured */
/* Native CSS Nesting — BEM naming ต้องใช้ class ปกติ */
.card {
padding: 1rem;
&:hover { box-shadow: ...; }
& .card-title { font-size: 1.1rem; } /* ต้องเป็น descendent */
&.card--featured { border-color: #2563eb; } /* compound modifier */
}
Browser Support
Chrome 120+, Firefox 117+, Safari 17.2+ — ใช้งาน production ได้แล้ว
/* Feature detection */
@supports selector(&) {
/* nesting supported */
.card {
&:hover { transform: translateY(-2px); }
}
}