Khi nhiều quy tắc cùng nhắm vào một phần tử, quy tắc nào thắng? Bài này giải thích thuật toán cascade: nguồn gốc, độ ưu tiên (specificity), thứ tự, kế thừa, !important và @layer. Hiểu bài này sẽ hết cảnh “sửa CSS mà không ăn”.
- Tính được độ ưu tiên của bộ chọn
- Biết thứ tự quyết định khi hòa
- Hiểu thuộc tính nào được kế thừa và các từ khóa inherit, initial, unset, revert
- Dùng @layer thay cho !important
- Specificity
- Trọng số của bộ chọn, viết dạng (id, class, thẻ).
- Kế thừa
- Phần tử con nhận giá trị từ cha nếu thuộc tính cho phép.
- @layer
- Tầng cascade do bạn đặt thứ tự.
Một số khái niệm
Trình duyệt quyết định theo thứ tự: (1) nguồn gốc và mức quan trọng (CSS mặc định của trình duyệt < CSS của bạn < !important), (2) tầng @layer, (3) độ ưu tiên của bộ chọn, (4) thứ tự xuất hiện: quy tắc viết sau thắng.
| Thành phần | Trọng số | Ví dụ |
|---|---|---|
| Style nội tuyến | 1-0-0-0 | style="..." |
| Id | 0-1-0-0 | #menu |
| Class, thuộc tính, pseudo-class | 0-0-1-0 | .nut, [type], :hover |
| Thẻ, pseudo-element | 0-0-0-1 | p, ::before |
| *, :where() | 0 | không cộng |
Ví dụ: #menu .muc a:hover = 1 id, 2 (class + pseudo-class), 1 thẻ → (1,2,1). So sánh từ trái sang phải: 1 id luôn thắng 100 class.
Thử nghiệm độ ưu tiên
<style>
p { color: gray; } /* (0,0,1) */
.ghi-chu { color: blue; } /* (0,1,0) thắng p */
#chinh .ghi-chu { color: green; } /* (1,1,0) thắng .ghi-chu */
p.ghi-chu { color: orange; } /* (0,1,1) thua #chinh .ghi-chu */
.lop { color: purple; }
.lop { color: crimson; } /* cùng độ ưu tiên: viết sau thắng */
</style>
<div id="chinh"><p class="ghi-chu">Màu gì? (xanh lá)</p></div>
<p class="ghi-chu">Màu gì? (cam)</p>
<p class="lop">Màu gì? (đỏ thẫm)</p>Kế thừa và từ khóa toàn cục
<style>
.cha { color: #1565c0; font-family: Georgia, serif; border: 2px dashed #1565c0; padding: 8px; }
.con-1 { border: inherit; } /* ép kế thừa border */
.con-2 { color: initial; } /* về giá trị mặc định ban đầu (đen) */
.con-3 { all: unset; } /* bỏ mọi giá trị đặt cho phần tử */
</style>
<div class="cha">Chữ màu xanh, font Georgia (kế thừa sang con)
<p>Đoạn con: kế thừa màu và font, KHÔNG kế thừa viền</p>
<p class="con-1">con-1: border: inherit</p>
<p class="con-2">con-2: color: initial</p>
</div>Thuộc tính về chữ (color, font-*, line-height, text-align, letter-spacing, visibility, cursor) thường được kế thừa; thuộc tính về hộp (margin, padding, border, background, width) thì không.
@layer và !important
/* Khai báo thứ tự tầng: tầng sau ưu tiên hơn, bất kể độ ưu tiên bộ chọn */
@layer reset, thu-vien, thanh-phan, tien-ich;
@layer thu-vien {
#app .btn { background: gray; } /* độ ưu tiên cao nhưng ở tầng thấp */
}
@layer thanh-phan {
.btn { background: #2e7d32; } /* thắng, vì tầng thanh-phan sau thu-vien */
}
/* CSS không nằm trong tầng nào ưu tiên hơn mọi tầng */
.an-di { display: none !important; } /* chỉ dùng cho lớp tiện ích thật sự */!important đảo ngược thứ tự và rất khó gỡ khi dự án lớn; Bootstrap và Tailwind v4 đều dùng @layer để CSS của bạn ghi đè thư viện dễ dàng.
Trong DevTools, rê chuột lên bộ chọn ở ngăn Styles để xem độ ưu tiên (Specificity) dạng (a, b, c).
Đừng “chữa cháy” bằng cách thêm id hoặc !important mỗi khi CSS không ăn; hãy tìm quy tắc đang thắng trong DevTools rồi viết bộ chọn có độ ưu tiên vừa đủ.
- Tính độ ưu tiên của:
ul li a,.menu a:hover,#dau .logo img. - Tạo 3 quy tắc xung đột và dự đoán màu cuối cùng trước khi xem.
- Viết lại một đoạn CSS dùng !important bằng @layer.
- Kiểm tra thuộc tính nào kế thừa bằng cách đặt chúng cho body.
- Thứ tự: nguồn gốc/!important → @layer → độ ưu tiên → thứ tự viết.
- Độ ưu tiên (id, class, thẻ); nội tuyến cao nhất.
- Thuộc tính chữ kế thừa; thuộc tính hộp không.
- inherit, initial, unset, revert, @layer thay !important.