Has() selector: CSS giờ đã có parent selector

:has()relational pseudo-class cho phép chọn một phần tử dựa trên những gì nó chứa hoặc đứng kề. Nói nôm na, đây là “parent selector” mà cộng đồng chờ đợi nhiều năm: bạn có thể style cha, tổ tiên, hay anh chị em đứng trước dựa vào trạng thái của phần tử con/tiếp theo. Kết quả là CSS sạch hơn, ít JS hơn, UI/UX phản hồi đúng ngữ cảnh.

Has() selector: CSS giờ đã có parent selector

Cú pháp cơ bản và tư duy sử dụng

Dạng tổng quát: A:has(B) → chọn A nếu A B khớp selector bên trong. B là selector tương đối (descendant, child, sibling…)

/* Style .card nếu bên trong có <img> */
.card:has(img) { border: 1px solid var(--accent); }

/* Chỉ khi .card có > h2 ở trực tiếp */
.card:has(> h2) { padding-top: 0.75rem; }

/* Nav item “đang active”: .nav li có <a aria-current> */
.nav li:has(> a[aria-current="page"]) { font-weight: 700; }

Form UX không cần JS

Chuyển trạng thái nhóm field theo input bên trong, làm nổi bật lỗi hợp lệ/không hợp lệ:

.field:has(input:focus) { outline: 2px solid var(--focus); }
.field:has(input:invalid) .error { display: block; }
.field:has(input:valid) .hint { opacity: .4; }

Style “phần tử đứng trước” nhờ sibling

:has() kiểm tra sibling phía sau để chọn phần tử hiện tại. Đây là mẹo để style phần tử đứng trước một phần tử “đặc biệt”.

/* Tô màu item đứng trước item đang active */
.item:has(+ .item--active) { border-right: 2px solid var(--accent); }

/* Breadcrumb: ẩn dấu chia khi phần tử sau là phần cuối */
.breadcrumb li:has(+ li[aria-current="page"])::after { content: "/"; }
.breadcrumb li[aria-current="page"]::after { content: ""; }

Card layout linh hoạt theo nội dung

Component tự điều chỉnh layout dựa vào nội dung thực tế, không lệ thuộc viewport:

.product-card { display: grid; grid-template-columns: 1fr; gap: 12px; }

/* Nếu có cả ảnh và badge thì chuyển sang layout 2 cột */
.product-card:has(img):has(.badge) {
  grid-template-columns: 120px 1fr;
  align-items: start;
}

/* Nếu có video thì ưu tiên aspect-ratio lớn hơn */
.product-card:has(video) video { aspect-ratio: 16 / 9; }

Menu thông minh theo trạng thái con

Đóng/mở submenu, đổi icon chỉ thuần CSS:

.menu__item:has(> .submenu[open]) > .toggle { transform: rotate(90deg); }
.menu__item:has(> .submenu[open]) { background: var(--soft); }

Kết hợp với :not(), :is(), :where() để viết selector gọn

/* Container có form nhưng KHÔNG có nút submit bị disabled */
.panel:has(form):has(:not(button[type="submit"]:disabled)) { box-shadow: 0 0 0 1px var(--ok); }

/* Bất kỳ section có tiêu đề h2/h3 */
section:has(:is(h2, h3)) { scroll-margin-top: 80px; }

Container Queries + :has(): component “tự hiểu bối cảnh”

Viết style theo kích thước container và cấu trúc bên trong một cách tinh gọn:

.card-wrap { container-type: inline-size; container-name: card; }

@container card (max-width: 420px) {
  .card:has(.media) { grid-template-columns: 1fr; }
  .card:has(.actions) .actions { justify-content: stretch; }
}

Hiệu năng và best practices

  • Scope selector: Luôn giới hạn phạm vi (ví dụ .card:has(img) thay vì *:has(img)).
  • Tránh lồng quá sâu: Nhiều :has() lồng nhau làm selector phức tạp và khó debug.
  • Dùng cho logic UI rõ ràng: Trạng thái focus/valid, hiện/ẩn block, nhánh layout theo nội dung thực.
  • Kiểm tra trong DevTools: Mở tab “Elements” và thử bật tắt class/DOM để xem rule khớp.

Khả năng tương thích và fallback

:has() đã được hỗ trợ rộng rãi trên trình duyệt hiện đại. Với môi trường cũ, cân nhắc:

  • Progressive enhancement: Tính năng chính chạy được; hiệu ứng nâng cao dùng :has() để tăng UX.
  • Class hook từ server/JS: Nếu cần tương thích tuyệt đối, thêm class vào parent như .has-img để thay thế.

Tóm tắt nhanh

  • :has() cho phép viết “parent selector” thực dụng: chọn cha/tổ tiên dựa vào con hoặc sibling theo sau.
  • Giảm phụ thuộc JS cho các logic UI điển hình: form state, menu, card layout theo nội dung.
  • Kết hợp tốt với Container Queries, Grid/Flex, và các pseudo-class hiện đại.
  • Viết selector có phạm vi rõ ràng để đảm bảo hiệu năng và dễ bảo trì lâu dài.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...