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

Category: guide

CSS Cascade Layers — จัดการ Specificity แบบมีระบบ

@layer ช่วยควบคุมลำดับความสำคัญของ CSS โดยไม่ต้องพึ่ง specificity hack หรือ !important

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

สารบัญ

ปัญหาที่ CSS Cascade Layers แก้

CSS ปกติใช้ specificity ตัดสินว่า rule ไหนชนะ:

/* specificity: 0-1-0 */
.button { color: blue; }

/* specificity: 1-0-0 — ชนะเสมอแม้จะมาทีหลัง */
#special { color: red; }

/* ต้องใช้ !important เพื่อ override — เป็นการแก้ปัญหาที่ผิดวิธี */
.button { color: green !important; }

เมื่อ codebase ใหญ่ขึ้น specificity wars กลายเป็นปัญหาใหญ่ โดยเฉพาะเมื่อใช้หลาย CSS library ร่วมกัน

@layer แก้โดยให้ layer ที่กำหนดทีหลังมีความสำคัญกว่าเสมอ โดยไม่สนใจ specificity ของ rule ใน layer เก่า


Syntax พื้นฐาน

/* กำหนดลำดับ layer ก่อน — layer ทีหลัง = สำคัญกว่า */
@layer base, components, utilities;

@layer base {
  button { color: blue; padding: 0.5rem 1rem; }
}

@layer components {
  .btn { color: white; background: #2563eb; }
}

@layer utilities {
  .text-red { color: red; }
}

Rule ใน utilities ชนะ components ซึ่งชนะ baseไม่ว่า specificity ของ rule ข้างในจะเป็นเท่าไหร่

@layer base {
  #very-specific-id { color: red; } /* specificity 1-0-0 แต่อยู่ใน base */
}

@layer utilities {
  .u-blue { color: blue; } /* specificity 0-1-0 แต่ utilities ชนะ */
}

/* .u-blue ชนะ #very-specific-id เพราะ layer ลำดับสูงกว่า */

กำหนดลำดับ Layer

/* วิธีที่ 1: ประกาศลำดับก่อน */
@layer reset, base, theme, components, utilities, overrides;

/* วิธีที่ 2: ลำดับตามที่ @layer ปรากฏครั้งแรก */
/* ไม่แนะนำ — อ่านยากกว่า */

แนะนำ: ประกาศลำดับทั้งหมดที่บรรทัดแรกของ CSS file เสมอ เพื่อให้เห็น architecture ชัดเจน


รูปแบบ Layer สำหรับโปรเจคทั่วไป

@layer
  reset,       /* normalize browser defaults */
  base,        /* body, headings, links, typography */
  theme,       /* CSS custom properties, design tokens */
  layout,      /* grid, container, sidebar */
  components,  /* buttons, cards, forms */
  utilities,   /* helper classes, spacing, text */
  overrides;   /* page-specific overrides, dark mode adjustments */

ใช้กับ @import

/* Import ทั้ง library เข้า layer เดียว — specificity ของ library ไม่ส่งผลข้างนอก */
@import url('normalize.css') layer(reset);
@import url('prism.css') layer(syntax);

@layer components {
  /* สามารถ override prism.css ได้เลย โดยไม่ต้องรู้ว่า selector มัน specific แค่ไหน */
  code { font-size: 0.9em; }
}

Anonymous Layer

ถ้าไม่ต้องการตั้งชื่อ layer ใช้ @layer เปล่า — ไม่สามารถเพิ่มเนื้อหาทีหลังได้:

@layer {
  /* anonymous layer — ไม่มีชื่อ ไม่สามารถอ้างถึงทีหลัง */
  .some-style { color: blue; }
}

Layer ซ้อน Layer

@layer components {
  @layer button {
    .btn { padding: 0.5rem; }
  }

  @layer card {
    .card { border-radius: 8px; }
  }
}

/* อ้างถึง nested layer จากภายนอกด้วย dot notation */
@layer components.button {
  .btn-lg { padding: 0.75rem 1.5rem; }
}

CSS ที่อยู่นอก Layer

CSS ที่ไม่ได้อยู่ใน @layer ใดๆ มีความสำคัญสูงสุด เสมอ:

@layer utilities {
  .text-blue { color: blue !important; }
}

/* นอก layer — ชนะ .text-blue แม้ไม่มี !important */
.my-special { color: red; }

กฎ: ใส่ทุกอย่างใน layer เสมอ ถ้าต้องการ override ใช้ layer ที่สูงกว่า ไม่ใช่เขียน CSS นอก layer


Pattern: Utility-First ที่ควบคุมได้

@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  body { margin: 0; }
}

@layer base {
  h1, h2, h3 { font-weight: 700; line-height: 1.35; }
  a { color: #2563eb; }
}

@layer components {
  .card {
    background: white;
    border-radius: 12px;
    padding: 1.5rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  }
}

@layer utilities {
  .mt-4  { margin-top: 1rem; }
  .p-0   { padding: 0; }
  .text-center { text-align: center; }
  /* utilities ชนะ components เสมอ — แบบ Tailwind แต่ไม่ต้อง !important */
}

Browser Support

รองรับใน Chrome 99+, Firefox 97+, Safari 15.4+ — ใช้งานได้ production ตั้งแต่ปี 2022

/* Fallback pattern ถ้าต้องการ support เก่า */
@supports (not (selector(:is(a,b)))) {
  /* CSS ทางเลือกสำหรับ browser เก่า */
}

สำหรับ browser ที่ไม่รองรับ @layer สามารถใช้ PostCSS plugin postcss-cascade-layers เพื่อ polyfill ได้


เปรียบเทียบกับวิธีเดิม

วิธีปัญหา
เพิ่ม class specificitycascade ซับซ้อนขึ้นเรื่อยๆ
ใช้ !importantชนะทุกอย่าง แต่ debug ยาก
BEM namingต้องตั้งชื่อระวัง ไม่ได้แก้ root cause
CSS Modulesscoping เฉพาะ component, ไม่จัดการ global order
@layerควบคุม order ได้ชัดเจน, specificity ไม่สำคัญอีกต่อไป