Bài 8. CSS mới: :has, container, nesting

CSS ngày nay làm được nhiều việc trước kia cần JavaScript: chọn theo con, truy vấn theo khung chứa, lồng quy tắc.

40 phútTrung cấp
Bạn sẽ học được
  • Dùng :has() chọn phần tử cha theo con
  • Dùng container queries
  • Viết CSS lồng nhau (nesting)
  • Dùng dialog và popover không cần thư viện
Thuật ngữ cần biết
Container query
Điều kiện CSS theo kích thước của khung chứa, không phải toàn cửa sổ.
Nesting
Viết quy tắc con bên trong quy tắc cha.
Popover
Thuộc tính HTML tạo hộp nổi, tự quản lý đóng/mở.

Khái niệm và lý thuyết

Trong nhiều năm, những việc dưới đây cần JavaScript hoặc thư viện. Nay trình duyệt hỗ trợ sẵn:

Tính năngGiải quyết vấn đề gì
:has()Chọn phần tử cha dựa trên con (selector "cha")
Container queryThành phần đổi bố cục theo khung chứa, không theo cả cửa sổ
CSS nestingViết quy tắc lồng nhau, mã gọn như Sass
dialog, popoverHộp thoại, menu nổi có sẵn, xử lý được bàn phím và lớp phủ
aspect-ratio, gap, clamp()Tỉ lệ khung, khoảng cách, cỡ chữ linh hoạt

Container query là bước tiến lớn của thiết kế đáp ứng: trước đây media query chỉ biết chiều rộng cửa sổ, nên cùng một thẻ đặt trong thanh bên và trong vùng chính vẫn bị ép cùng bố cục. Giờ thẻ "tự biết" chỗ đặt của nó và thích nghi, giúp component thật sự tái sử dụng được.

Khi dùng tính năng mới, hãy kiểm tra mức hỗ trợ trên caniuse.com và dùng @supports để cung cấp phương án dự phòng.

:has() - bộ chọn "cha"

/* thẻ có chứa ảnh thì chia hai cột */
.the:has(img) { display: grid; grid-template-columns: 120px 1fr; }
/* nhãn đổi màu khi ô nhập không hợp lệ */
label:has(+ input:invalid) { color: crimson; }

Container queries

.khung { container-type: inline-size; }
.the { display: block; }
@container (min-width: 480px) {
  .the { display: flex; gap: 16px; }
}

Thẻ tự đổi bố cục theo chỗ đặt: trong thanh bên hẹp thì xếp dọc, trong vùng chính rộng thì xếp ngang.

CSS lồng nhau

.menu {
  display: flex;
  a {
    padding: 8px 14px;
    &:hover { background: #e8f5e9; }
    &.active { font-weight: 700; }
  }
}

dialog và popover

<dialog id="hop">
  <p>Bạn chắc chắn muốn xóa?</p>
  <form method="dialog"><button>Hủy</button><button value="ok">Xóa</button></form>
</dialog>
<button onclick="hop.showModal()">Xóa</button>

<button popovertarget="goi-y">Gợi ý</button>
<div id="goi-y" popover>Nội dung hiện khi bấm, bấm ngoài là đóng.</div>

Ví dụ: hộp thoại không cần JavaScript phức tạp

<dialog id="hop">
  <p>Bạn có chắc muốn xóa?</p>
  <form method="dialog"><button>Đóng</button></form>
</dialog>
<button onclick="hop.showModal()">Xóa</button>

Thẻ dialog có sẵn lớp nền mờ, bẫy bàn phím và nút Esc để đóng. Trước đây bạn phải tự viết rất nhiều mã để có đủ những thứ này. Trước khi dùng tính năng mới, hãy tra độ hỗ trợ tại Can I use.

Một vài tính năng hay khác

  • aspect-ratio: 16/9 giữ tỉ lệ khung.
  • gap cho cả flex và grid.
  • color-mix() trộn màu, text-wrap: balance cân tiêu đề.
  • scroll-snap, position: sticky làm cuộn đẹp.
  • @layer kiểm soát thứ tự ưu tiên.
Tình huống: Thẻ sản phẩm dùng được mọi nơi

Cùng một component thẻ nhưng đặt vào trang chủ, thanh bên và giỏ hàng. Container query giúp thẻ tự co giãn mà không cần viết lại CSS cho từng chỗ.

Mẹo hay

Kiểm tra mức hỗ trợ trình duyệt tại caniuse.com; các tính năng trong bài đều có trong trình duyệt hiện đại.

Lưu ý

Nếu cần hỗ trợ trình duyệt rất cũ, hãy dùng @supports để dự phòng.

Thực hành
  1. Dùng :has() đổi viền khung biểu mẫu khi có ô sai.
  2. Tạo hộp thoại xác nhận bằng thẻ dialog.
  3. Viết lại một đoạn CSS dài bằng nesting.
Tóm tắt bài học
  • :has(), container queries, nesting đều dùng được ngay.
  • dialog và popover thay thế nhiều thư viện modal.
  • Luôn kiểm tra hỗ trợ trình duyệt.