CSS Specificity và Cascade: Hiểu Đúng Để Tránh Bug Layout
Khám phá cách CSS Specificity và Cascade hoạt động — cách trình duyệt quyết định style nào được áp dụng, cách tính specificity, mẹo quản lý cascade, và tránh các lỗi thường gặp.

Giới thiệu
Bạn đã bao giờ viết một dòng CSS nhưng nó không được áp dụng? Hoặc bạn phải dùng !important để ghi đè một style khác mà không hiểu tại sao? Nếu câu trả lời là “có”, thì bạn đang gặp vấn đề với CSS Specificity và Cascade.
Đây là hai khái niệm nền tảng nhất trong CSS — nhưng cũng là nguồn gốc của nhiều rắc rối nhất. Hiểu đúng về chúng sẽ giúp bạn:
- 🎯 Kiểm soát style nào được áp dụng mà không cần hack
- 🧹 Code sạch hơn, giảm
!importantvà lặp selector - 🔍 Debug nhanh hơn khi style không hoạt động như mong đợi
- 📦 Dễ bảo trì hơn khi dự án lớn dần
Trong bài viết này, chúng ta sẽ đi sâu vào từng khái niệm, từ cơ bản đến nâng cao, với nhiều ví dụ thực tế.
Cascade là gì?
Cascade (thác nước) là cơ chế mà CSS sử dụng để giải quyết xung đột khi nhiều style cùng nhắm vào một phần tử. Nó hoạt động theo một thứ tự ưu tiên rõ ràng:
Thứ tự ưu tiên trong Cascade
Từ thấp đến cao:
- User Agent Styles — Style mặc định của trình duyệt (mặc định của
<h1>là font-size lớn, margin, v.v.) - User Styles — Style do người dùng cài đặt (ví dụ: zoom, font-size trong trình duyệt)
- Author Styles — Style do bạn (developer) viết
- !important Author Styles — Style của bạn với
!important - !important User Styles — Style của người dùng với
!important(rất hiếm) - Animation — Style từ CSS Animations
- Transition — Style từ CSS Transitions (ưu tiên cao nhất)
📌 Mẹo: Trong thực tế, bạn chỉ cần quan tâm đến Author Styles, !important, và Animation/Transition. Phần còn lại hiếm khi gây ảnh hưởng.
Các yếu tố trong Cascade
Khi trình duyệt quyết định style nào thắng, nó xem xét 4 yếu tố theo thứ tự:
- Importance & Origin —
!importanthay không? Từ author hay user? - Specificity — Selector nào cụ thể hơn?
- Order of Appearance — Style nào xuất hiện sau?
- Initial/Inherited — Giá trị mặc định hay kế thừa?
Specificity là gì?
Specificity (độ đặc hiệu) là cách CSS tính điểm cho mỗi selector để xác định selector nào “mạnh” hơn.
Cách tính Specificity
Specificity được biểu diễn dưới dạng bộ 4 số: (a, b, c, d)
| Thành phần | Điểm | Ví dụ |
|---|---|---|
| Inline styles | (1, 0, 0, 0) | style="color: red" |
| ID selectors | (0, 1, 0, 0) | #header, #main |
| Class, attribute, pseudo-class | (0, 0, 1, 0) | .nav, [type="text"], :hover |
| Element, pseudo-element | (0, 0, 0, 1) | div, p, ::before |
So sánh từ trái sang phải: số nào lớn hơn ở vị trí đầu tiên thì thắng.
Ví dụ tính Specificity
/* (0, 0, 0, 1) — 1 element */
div { color: red; }
/* (0, 0, 1, 0) — 1 class */
.card { color: blue; }
/* (0, 1, 0, 0) — 1 ID */
#header { color: green; }
/* (0, 0, 2, 1) — 2 classes + 1 element */
div.card.active { color: purple; }
/* (0, 1, 1, 1) — 1 ID + 1 class + 1 element */
div#header.nav { color: orange; }
/* (1, 0, 0, 0) — inline style */
/* <div style="color: pink"> */
Ai thắng?
<div id="content" class="post">
<p id="title" class="heading" style="color: green">Hello World</p>
</div>
p { color: red; } /* (0,0,0,1) */
.heading { color: blue; } /* (0,0,1,0) */
#title { color: purple; } /* (0,1,0,0) */
p#title { color: orange; } /* (0,1,0,1) */
p.heading { color: teal; } /* (0,0,1,1) */
#content p { color: pink; } /* (0,1,0,1) */
#content #title { color: navy; } /* (0,2,0,0) */
Kết quả: Màu xanh lá (style="color: green") — inline style (1,0,0,0) thắng tất cả.
Lỗi thường gặp với Specificity
1. Chuỗi selector quá dài
/* ❌ Xấu — quá dài, khó đọc, khó ghi đè */
body div#main.container article.content p.text span.highlight { ... }
/* ✅ Tốt — dùng class ngắn gọn */
.highlight { ... }
2. ID selector trong CSS
/* ❌ Tránh — ID có specificity quá cao, khó ghi đè */
#sidebar { ... }
/* ✅ Tốt hơn — dùng class */
.sidebar { ... }
3. Lạm dụng !important
/* ❌ Tránh — tạo ra vòng xoáy !important */
.btn { color: red !important; }
.btn.special { color: blue !important; } /* phải thêm !important nữa */
/* ✅ Tốt — dùng specificity tự nhiên */
.btn { color: red; }
.btn.special { color: blue; }
!important — Khi nào nên dùng?
!important là “vũ khí hạt nhân” của CSS. Nó ghi đè mọi quy tắc specificity (trừ inline style cùng cấp).
Khi nào nên dùng
- Utility classes: Các class tiện ích như
.hidden { display: none !important; } - Override từ third-party: Khi cần ghi đè style từ thư viện không thể sửa
- State styles: Các trạng tháo như
.is-activetrong JavaScript
Khi nào KHÔNG nên dùng
- Style thông thường — dùng specificity tự nhiên
- Component styles — dùng BEM hoặc CSS Modules
- Khi lười tính specificity — học cách tính thay vì dùng
!important
Kế thừa (Inheritance) trong CSS
Một số thuộc tính CSS tự động kế thừa từ cha sang con:
Thuộc tính kế thừa
/* Các thuộc tính thường kế thừa */
color /* màu chữ */
font-family /* font chữ */
font-size /* cỡ chữ */
font-weight /* độ đậm */
line-height /* chiều cao dòng */
text-align /* căn chỉnh văn bản */
visibility /* hiển thị */
cursor /* con trỏ chuột */
Thuộc tính KHÔNG kế thừa
/* Các thuộc tính KHÔNG kế thừa */
margin /* lề */
padding /* đệm */
border /* viền */
background /* nền */
width /* chiều rộng */
height /* chiều cao */
display /* kiểu hiển thị */
position /* định vị */
Ép kế thừa với inherit
/* Ép một phần tử con kế thừa từ cha */
.child {
border: inherit; /* thừa kế border từ cha */
margin: inherit; /* thừa kế margin từ cha */
background: inherit; /* thừa kế background từ cha */
}
Giá trị đặc biệt
.parent {
color: blue;
border: 2px solid black;
}
.child {
/* Kế thừa giá trị từ cha */
color: inherit; /* → blue */
/* Lấy giá trị mặc định ban đầu (không inherit) */
border: initial; /* → none (giá trị mặc định của border) */
/* Lấy giá trị mặc định của thuộc tính (không inherit) */
margin: unset; /* → 0 (vì margin không inherit) */
color: unset; /* → inherit (vì color inherit) */
/* Revert — quay về style của user agent */
all: revert; /* reset tất cả về mặc định trình duyệt */
}
CSS Cascade Layers (@layer)
CSS Cascade Layers là tính năng mới cho phép bạn kiểm soát thứ tự cascade ở cấp độ cao hơn:
/* Định nghĩa layers theo thứ tự ưu tiên (từ thấp đến cao) */
@layer reset, base, components, utilities;
/* Layer reset — ưu tiên thấp nhất */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
/* Layer base */
@layer base {
h1 { font-size: 2rem; }
p { line-height: 1.6; }
}
/* Layer components */
@layer components {
.card { padding: 2rem; border-radius: 8px; }
.btn { display: inline-block; padding: 0.5rem 1rem; }
}
/* Layer utilities — ưu tiên cao nhất */
@layer utilities {
.hidden { display: none !important; }
.text-center { text-align: center; }
}
Lợi ích của @layer:
- ✅ Kiểm soát thứ tự cascade rõ ràng
- ✅ Không cần tăng specificity để ghi đè
- ✅ Dễ bảo trì khi dự án lớn
- ✅ Tương thích với hầu hết trình duyệt hiện đại
Mẹo và Best Practices
1. Dùng BEM để kiểm soát specificity
/* BEM — Block Element Modifier */
.card { } /* block */
.card__title { } /* element */
.card__title--active { } /* modifier */
/* Tất cả đều có specificity (0,0,1,0) — dễ kiểm soát */
2. Tránh nesting quá sâu
/* ❌ Khó kiểm soát specificity */
.main .sidebar .widget .list .item .link { ... }
/* ✅ Dễ kiểm soát */
.widget-link { ... }
3. Dùng :where() để reset specificity
/* :where() luôn có specificity (0,0,0,0) */
:where(.card) .title { color: blue; }
/* specificity: (0,0,0,1) — chỉ tính .title */
/* So sánh với :is() */
:is(.card) .title { color: red; }
/* specificity: (0,0,1,1) — tính cả .card và .title */
4. Dùng :is() và :not() thông minh
/* :is() lấy specificity cao nhất của tham số */
:is(#header, .section) .title { } /* specificity: (0,1,0,1) */
/* :not() lấy specificity của tham số bên trong */
:not(.disabled) .btn { } /* specificity: (0,0,1,1) */
5. Kỹ thuật “Specificity Override” có chủ đích
/* Tăng specificity có kiểm soát bằng cách lặp class */
.btn.btn { } /* (0,0,2,0) — gấp đôi */
.btn.btn.btn { } /* (0,0,3,0) — gấp ba */
Bảng Debug Specificity Nhanh
| Selector | Specificity | Điểm |
|---|---|---|
* | (0,0,0,0) | 0 |
h1 | (0,0,0,1) | 1 |
ul li | (0,0,0,2) | 2 |
ul li a | (0,0,0,3) | 3 |
.class | (0,0,1,0) | 10 |
[data-type] | (0,0,1,0) | 10 |
:hover | (0,0,1,0) | 10 |
.class.class | (0,0,2,0) | 20 |
#id | (0,1,0,0) | 100 |
#id.class | (0,1,1,0) | 110 |
#id #id | (0,2,0,0) | 200 |
style="..." | (1,0,0,0) | 1000 |
!important | override | ∞ |
💡 Mẹo: DevTools của Chrome/Firefox hiển thị specificity ngay trong tab Styles. Bấm chuột phải vào phần tử → Inspect → Styles panel → xem phần “Specificity” ở cuối mỗi rule.
Kết luận
CSS Specificity và Cascade không phải là “kẻ thù” — chúng là công cụ. Khi bạn hiểu cách chúng hoạt động:
- 🎯 Bạn biết chính xác style nào sẽ thắng
- 🧹 Code của bạn sạch hơn, không cần
!important - 🔍 Debug nhanh hơn với DevTools
- 📦 Dễ dàng mở rộng dự án mà không sợ “style conflict”
Nguyên tắc vàng: Hãy giữ specificity càng thấp càng tốt, và chỉ tăng khi thực sự cần. Dùng class thay vì ID, dùng @layer để tổ chức, và tránh !important như tránh lửa — trừ khi bạn thực sự cần nó.
Tài liệu tham khảo
- MDN: CSS Specificity
- MDN: CSS Cascade
- MDN: CSS Inheritance
- MDN: CSS Cascade Layers
- Specificity Calculator — công cụ tính specificity trực tuyến