Trước đây, bộ chọn CSS chỉ có thể truyền xuống dưới – bạn có thể tạo kiểu cho phần tử con dựa trên phần tử mẹ, nhưng không thể tạo kiểu cho phần tử mẹ dựa trên phần tử con. Lớp giả :has() thay đổi điều này, cho phép bạn tạo kiểu có điều kiện cho một phần tử vùng chứa tuỳ thuộc vào sự hiện diện hoặc trạng thái của các phần tử con.
Bằng cách kết hợp :has() với các lớp giả trạng thái (như :checked, :focus, :valid, :invalid hoặc :not()), bạn có thể tạo các thành phần giao diện người dùng tương tác, phức tạp hoàn toàn bằng CSS mà không cần JavaScript để bật/tắt các lớp "bộ sửa đổi" (như .is-active hoặc .has-error) trên các phần tử mẹ.
Điều này đặc biệt hữu ích cho các thành phần cần phản hồi các hoạt động tương tác nội bộ, chẳng hạn như nút bật/tắt giao diện được bản địa hoá phản ứng với hộp đánh dấu (:checked), nhóm biểu mẫu làm nổi bật lỗi (:invalid) hoặc thẻ nâng lên khi đường liên kết con được lấy tiêu điểm (:focus-within).
Triển khai kiểu vùng chứa dựa trên trạng thái
BẮT BUỘC: Bạn phải sử dụng bộ chọn :has() trên phần tử vùng chứa để phát hiện trạng thái cụ thể (ví dụ: :checked, :focus, :invalid) của phần tử con tương tác.
Cách tạo một thành phần thay đổi kiểu dựa trên trạng thái của thành phần con:
- Xác định kiểu mặc định: Đặt các biến CSS trên vùng chứa để xác định trạng thái cơ sở của vùng chứa.
- Áp dụng cơ chế ghi đè dựa trên trạng thái: Nhắm mục tiêu vùng chứa bằng
:has([child-selector]:[state])và xác định lại các biến CSS cho trạng thái đang hoạt động hoặc trạng thái thay thế.
Ví dụ: Một thành phần thay đổi giao diện dựa trên nút bật/tắt con.
/* 1. Define the default state on the component container */
.theme-card {
/* Using custom properties makes state-switching cleaner */
--card-bg: #ffffff;
--card-text: #333333;
--card-border: #cccccc;
background-color: var(--card-bg);
color: var(--card-text);
border: 1px solid var(--card-border);
/* Use a transition for smooth state changes */
transition: background-color 0.3s, color 0.3s;
}
/* 2. Apply styles when the child enters the specific state */
/* MANDATORY: Target the container and use :has() to check the descendant's state */
.theme-card:has(.theme-toggle:checked) {
/* Override the properties for the active or alternate state */
--card-bg: #222222;
--card-text: #f0f0f0;
--card-border: #555555;
}
/* You can also combine :has() and :not() to target specific negative states */
/* This selects a card that DOES NOT have a toggle in the checked state */
.theme-card:not(:has(.theme-toggle:checked)) {
/* Optional: Explicit styles for the unchecked state if needed */
}
<!-- The container element that receives the styling -->
<div class="theme-card">
<!-- The child element whose state controls the parent -->
<label>
<input type="checkbox" class="theme-toggle">
Enable Dark Mode
</label>
<h2>Card Title</h2>
<p>The style of this entire card is controlled by the checkbox above.</p>
</div>
Mẹo về hiệu suất: Khi sử dụng :has(), hãy đặt phạm vi của bộ chọn cho vùng chứa cụ thể nhất có thể (chẳng hạn như .theme-card). Tránh neo bộ chọn vào các phần tử cấp cao như body:has(...) nếu các thay đổi về kiểu được bản địa hoá, vì các truy vấn :has() rộng có thể kích hoạt nhiều lần tính toán lại bố cục hơn.
Chiến lược dự phòng
Nếu kiểu dựa trên trạng thái là yếu tố quan trọng đối với trải nghiệm người dùng hoặc bố cục trang, thì bạn phải cung cấp một phương án dự phòng cho các trình duyệt không hỗ trợ bộ chọn :has(). Đối với các hiệu ứng trang trí thuần tuý, :has() có thể được sử dụng làm tính năng cải tiến tăng dần mà không cần dự phòng.
BẮT BUỘC: Khi triển khai phương án dự phòng cho các tính năng quan trọng, bạn phải sử dụng @supports not selector(:has(*)) trong CSS để xác định phương án dự phòng dựa trên lớp truyền thống. Nếu thay đổi trạng thái quan trọng dựa vào hoạt động tương tác của người dùng, bạn cũng phải sử dụng một tập lệnh cùng dòng nhỏ có CSS.supports() để bật/tắt lớp đó dựa trên sự kiện JavaScript tương đương (ví dụ: change, focus, blur) đại diện cho thay đổi trạng thái.
/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
/* Define a traditional modifier class that applies the exact same overrides */
.theme-card.is-active {
--card-bg: #222222;
--card-text: #f0f0f0;
--card-border: #555555;
}
}
/* Fallback JavaScript for older browsers */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
const toggle = document.querySelector('.theme-toggle');
const card = document.querySelector('.theme-card');
if (toggle && card) {
// Manually toggle the fallback class when the input state changes
toggle.addEventListener('change', (e) => {
card.classList.toggle('is-active', e.target.checked);
});
}
}