Category: guide
CSS Cascade Layers — จัดการ Specificity แบบมีระบบ
@layer ช่วยควบคุมลำดับความสำคัญของ CSS โดยไม่ต้องพึ่ง specificity hack หรือ !important
สารบัญ
ปัญหาที่ 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 specificity | cascade ซับซ้อนขึ้นเรื่อยๆ |
ใช้ !important | ชนะทุกอย่าง แต่ debug ยาก |
| BEM naming | ต้องตั้งชื่อระวัง ไม่ได้แก้ root cause |
| CSS Modules | scoping เฉพาะ component, ไม่จัดการ global order |
@layer | ควบคุม order ได้ชัดเจน, specificity ไม่สำคัญอีกต่อไป |