ข้ามไปเนื้อหาหลัก

Category: reference

CSS Nesting — Native Nested Selectors

CSS Nesting ที่รองรับใน browser ตั้งแต่ Chrome 120+ ทำให้เขียน nested selectors ได้โดยไม่ต้องใช้ Sass — ครอบคลุม syntax, &, @nest, และ gotchas

· อ่านประมาณ 3 นาที

สารบัญ

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); }
  }
}