:has() trong CSS: Siêu Năng Lực Mới Giúp Bạn Định Dạng Website Bá Đạo!
Lê Lân
0
:has() Pseudo-class trong CSS: Cách Mạng Styling Dựa Trên Thành Viên Con
Mở Đầu
Pseudo-class :has() được xem là “parent selector” mà CSS từ trước đến nay thiếu vắng, mở ra những cơ hội mới cho việc định kiểu dựa trên cấu trúc DOM.
Trong quá trình phát triển web, việc chọn và style các phần tử dựa trên mối quan hệ với con con luôn là thách thức với CSS. :has() — một phần của CSS Selectors Level 4 — đã thay đổi cách thức đó bằng cách cho phép ta style một phần tử dựa trên sự hiện diện của các con cháu cụ thể. Bài viết này sẽ phân tích chi tiết về :has(), cách hoạt động, các trường hợp sử dụng thực tiễn và những điểm cần lưu ý khi áp dụng.
:has() Là Gì?
Định Nghĩa Cơ Bản
:has() là một pseudo-class quan hệ, nhằm chọn phần tử cha nếu phần tử đó có chứa một phần tử con thỏa mãn selector. Điều này khác biệt với các pseudo-class truyền thống như :hover hay :first-child vốn tập trung vào trạng thái hoặc vị trí của phần tử được chọn.
Cú pháp chuẩn:
selector:has(selector) {
/* styles */
}
Ví dụ:
div:has(p) {
border: 2px solid blue;
}
Đoạn code trên sẽ áp dụng viền xanh cho mọi thẻ <div> có ít nhất một thẻ <p> bên trong, không phân biệt độ sâu con cháu.
Bạn có thể hiểu :has() như một selector ngược, từ con trở về cha.
Phân Biệt với Các Pseudo-class Khác
:hover, :focus - dựa trên trạng thái.
:first-child - dựa trên vị trí so với anh chị em.
:has() - dựa trên mối quan hệ bố con, nội dung bên trong.
Hỗ Trợ Trình Duyệt và Mức Độ Ứng Dụng
Tính đến June 1, 2025, :has() được hỗ trợ tốt trên các trình duyệt chính như:
Trình duyệt
Phiên bản hỗ trợ tối thiểu
Chrome
105+
Firefox
105+
Safari
15.4+
Edge
105+
Bạn nên kiểm tra kỹ bằng CanIUse trước khi triển khai trên các sản phẩm yêu cầu hỗ trợ trình duyệt cũ.
Xử lý fallback
/* Fallback cho trình duyệt không hỗ trợ */
div {
border: 1px solid gray;
}
/* Trình duyệt hỗ trợ :has() */
@supports selector(:has(*)) {
div:has(p) {
border: 1px solid blue;
}
}
Cách :has() Hoạt Động
Thành Phần Truyền Vào (Selector Argument)
Có thể là bất kỳ selector hợp lệ nào: type selector (p), class (.active), ID (#header), attribute (a[href^="https"]) hoặc các tổ hợp phức tạp.
Không giới hạn độ sâu con cháu, không chỉ con trực tiếp.
Mục Tiêu (Parent Targeting)
Style áp dụng cho phần tử chứa con thỏa mãn selector bên trong :has()
Ví dụ: section:has(p) sẽ style phần tử <section> chứ không phải thẻ <p>.
Ví dụ minh họa
<section>
<h2>Tiêu đề</h2>
<p>Nội dung</p>
</section>
<section>
<h2>Tiêu đề khác</h2>
</section>
section:has(p) {
background-color: lightblue;
}
Chỉ phần tử <section> đầu tiên được áp dụng nền xanh nhạt do chứa <p>.
Các Trường Hợp Sử Dụng Thực Tiễn
1. Style Container Dựa Trên Thành Viên Con
article:has(img) {
border-left: 4px solid green;
}
Tô đậm các bài viết có hình ảnh, giúp người dùng dễ nhận biết.
2. Cải Thiện Trải Nghiệm Form Validation
.form-group:has(input:invalid) {
border: 1px solid red;
}
.form-group:has(input:valid) {
border: 1px solid green;
}
Không cần JavaScript, tự động báo lỗi qua viền đỏ cho nhóm chứa input không hợp lệ.
3. Tùy Chỉnh Giao Diện Component
.card:has(.btn.active) {
box-shadow: 0010pxrgba(0, 0, 255, 0.3);
}
Tạo hiệu ứng nổi bật cho card khi có nút đang hoạt động.
4. Tạo Kiểu Cho Anh Chị Em Kế Cận Phức Tạp
h2:has(+ p) {
margin-bottom: 0.5em;
}
Giảm khoảng cách cho tiêu đề <h2> nếu ngay sau là đoạn <p>.
5. Nâng Cao Khả Năng Truy Cập (Accessibility)
.section:has(.alert) {
outline: 2px dashed orange;
}
Tạo điểm nhấn cho các khu vực có cảnh báo, dễ nhìn hơn với người dùng cần trợ giúp thị giác.
Kết Hợp :has() Với Các Selector Khác
Nested :has()
article:has(> header:has(.featured)) {
background: #f0f0f0;
}
Chỉ chọn article có header trực tiếp chứa lớp .featured.
Kết hợp với :not()
div:not(:has(p)) {
color: gray;
}
Tô màu xám cho div không chứa thẻ p.
Kết hợp với trạng thái pseudo-classes
nav:has(a:hover) {
background: #eee;
}
Thay đổi nền khi các liên kết trong <nav> được hover.
Các Vấn Đề Hiệu Năng Cần Lưu Ý
:has() có thể gây ảnh hưởng tới hiệu suất nếu:
Selector trong :has() quá phức tạp, nhiều tầng lồng nhau.
Áp dụng trên toàn bộ DOM hoặc các phần tử với rất nhiều con.
DOM thường xuyên thay đổi, gây reflow liên tục.
Lời khuyên tối ưu:
Giữ selector đơn giản, ưu tiên mức độ chính xác thấp hơn.
Giới hạn phạm vi áp dụng trong các container cụ thể.
Theo dõi hiệu suất render bằng công cụ dành cho nhà phát triển trình duyệt.
Những Hạn Chế và Lưu Ý
1. Không thể tự tham chiếu (No Self-referencing)
:has() không thể chọn chính phần tử dựa trên bản thân nó, chỉ dựa trên con cháu.
2. Ảnh hưởng tới Specificity
Selector trong :has() ảnh hưởng tới độ ưu tiên, nên cân nhắc khi viết tránh gây xung đột CSS.
3. Đổi mới DOM thường xuyên
Mặc dù cơ chế tự động cập nhật, nhưng thay đổi DOM quá nhiều dễ gây lag do reflow.
4. Không phải selector cha “toàn diện”
Chỉ style phần tử được chọn, nếu muốn style con cần tiếp tục thêm selector bên ngoài.
div:has(.error) p { color: red; }
5. Fallback trình duyệt
Luôn cung cấp phong cách thay thế cho các trình duyệt chưa hỗ trợ.
Những Nguyên Tắc Vàng Khi Sử Dụng :has()
Chỉ dùng khi thật sự cần thiết, tránh lạm dụng.
Kết hợp với các tính năng mới của CSS như biến, container queries để tối ưu code.
Kiểm tra kỹ khả năng truy cập (accessibility) liên quan.
Thêm chú thích rõ ràng để người khác dễ hiểu selector phức tạp.
Theo dõi hiệu suất khi dự án mở rộng.
Ví Dụ Thực Tế
Giả sử một blog với các bài viết có video cần nổi bật:
Chỉ bài viết có video được tô nền gradient và bo góc để dễ nhận biết.
Kết Luận
:has() là một bước tiến lớn trong bộ công cụ CSS, cho phép style các phần tử theo cách trực quan, nhanh chóng và giảm thiểu sự phụ thuộc vào JavaScript. Từ việc validate form, tạo layout điều kiện cho đến trợ năng, :has() mang đến sức mạnh mới cho các lập trình viên front-end.
Tuy vậy, để khai thác hiệu quả, bạn cần cân nhắc kỹ về hiệu suất và tương thích trình duyệt. Hãy thử áp dụng trong dự án tiếp theo của bạn để tận hưởng sự tiện lợi của selector này.