Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

HTML & CSS Mới 2026: Container Queries, @layer và Layout Hiện Đại Không Cần JavaScript

Ếch Trendy
Ếch Trendy

2 tháng trước · 7 phút đọc

Tại sao CSS 2026 là bước ngoặt với frontend dev

Mình nhớ hồi mới làm dự án đầu tiên, cứ nghĩ responsive design = viết media query + một đống JavaScript để toggle class. Card co lại? JS. Sidebar ẩn hiện? JS. Typography thay đổi theo viewport? JS nốt. Sau 2 năm làm vậy, codebase trở nên lộn xộn kinh khủng.

Thật ra vấn đề không phải ở JavaScript. Vấn đề là mình dùng JS để làm những việc CSS đã có thể làm - chỉ là trình duyệt chưa hỗ trợ đủ. Đến 2025-2026, bức tranh thay đổi hoàn toàn.

345995 Browser support 2026 đã đủ mạnh để dùng các tính năng CSS mới trong production

Container Queries đạt 93-95% global browser support for @container size queries in 2024-2025 (Can I Use / caniuse.com) browser support toàn cầu. @layer được hỗ trợ trên tất cả evergreen browsers. :has() - cái selector từng bị gọi là "impossible" - giờ chạy được ở khắp nơi. Đây là thời điểm đúng để refactor cách bạn viết CSS.

Bài này mình sẽ đi qua từng tính năng với code thực tế, không lý thuyết suông.


Container Queries: responsive theo component, không theo viewport

Media queries có một điểm yếu cố hữu: chúng chỉ nhìn vào viewport. Card của bạn nằm trong sidebar hẹp hay main content rộng - media query không biết, không quan tâm.

Container Queries giải quyết đúng vấn đề này. Thay vì hỏi "viewport rộng bao nhiêu?", bạn hỏi "container của element này rộng bao nhiêu?"

/* Khai báo container */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

/* Style theo kích thước container */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1rem;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

345996 Cùng một component, layout tự thích nghi theo container - không cần JavaScript

Đây là use case cực kỳ thực tế. Giả sử bạn có component card dùng ở cả sidebar (250px) lẫn main grid (600px). Trước đây bạn phải tạo 2 class riêng hoặc viết JS để detect. Giờ chỉ cần container-type: inline-size là xong.

Container Queries với Units

Cái hay hơn nữa là CSS còn có container query units:

/* cqi = 1% chiều rộng của container */
.card__title {
  font-size: clamp(1rem, 4cqi, 1.75rem);
  /* Tự scale theo container, không theo viewport */
}

cqi, cqb, cqw, cqh - những unit này giúp typography và spacing tự adjust theo container của chúng, không phải screen. Một khi quen dùng, bạn sẽ không muốn quay lại vw nữa.


@layer: kiểm soát cascade không còn là ác mộng

Bao nhiêu lần bạn thêm !important chỉ vì không hiểu tại sao style bị override? Hoặc import thư viện vào rồi mất cả buổi chiều debug specificity?

@layer sinh ra để fix đúng vấn đề này.

/* Định nghĩa thứ tự ưu tiên - layer sau thắng layer trước */
@layer reset, base, components, utilities;

/* Reset styles - ưu tiên thấp nhất */
@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

/* Base styles */
@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.5;
  }
  
  h1, h2, h3 {
    font-weight: 600;
  }
}

/* Component styles */
@layer components {
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 0.375rem;
    font-weight: 500;
    cursor: pointer;
  }
  
  .btn-primary {
    background: #3b82f6;
    color: white;
  }
}

/* Utilities - ưu tiên cao nhất */
@layer utilities {
  .mt-4 { margin-top: 1rem; }
  .text-center { text-align: center; }
  .hidden { display: none; }
}

345997 @layer đặt ra thứ tự rõ ràng - không còn specificity war trong team

Bên cạnh việc tự tổ chức, @layer còn giúp khi import thư viện bên ngoài:

/* Đưa toàn bộ Bootstrap vào layer thấp */
@layer bootstrap {
  @import url('bootstrap.css');
}

/* Styles của bạn nằm ngoài layer - luôn thắng */
.custom-button {
  background: rebeccapurple; /* Luôn override Bootstrap */
}

Trước đây override Bootstrap là cả một nghệ thuật. Giờ chỉ cần đặt import của họ vào @layer, còn code của bạn để ngoài - specificity không còn là vấn đề nữa.

Nếu bạn muốn nắm vững CSS từ nền tảng trước khi vào các tính năng nâng cao này, khóa HTML CSS từ Zero đến Hero có thể giúp bạn build nền tốt.


:has() selector: CSS viết được logic mà trước đây chỉ JS làm được

:has() là cái selector mình đợi lâu nhất. Nó cho phép bạn style một element dựa trên con hoặc sibling của nó - thứ mà CSS trước đây không bao giờ làm được.

Ví dụ thực tế: form validation styling mà không cần JS.

/* Input có lỗi -> label đổi màu đỏ */
.form-group:has(input:invalid) label {
  color: #ef4444;
}

/* Card có ảnh -> thêm padding khác */
.card:has(img) {
  padding-top: 0;
}

/* Nav item đang active -> highlight parent */
.nav-section:has(.nav-item.active) {
  background: #f0f9ff;
  border-left: 3px solid #0ea5e9;
}

/* Grid tự điều chỉnh khi có ít hơn 3 items */
.grid-container:has(.grid-item:nth-child(3)) {
  grid-template-columns: repeat(3, 1fr);
}

.grid-container:not(:has(.grid-item:nth-child(3))) {
  grid-template-columns: repeat(2, 1fr);
}

345998 :has() selector - từng được gọi là impossible, giờ chạy trên mọi evergreen browser

Use case yêu thích của mình là sidebar layout. Trước đây phải dùng JS để detect xem sidebar có open không rồi toggle class cho main content:

/* Nếu sidebar đang open -> co main content lại */
body:has(.sidebar.open) .main-content {
  margin-left: 280px;
  transition: margin-left 0.3s ease;
}

/* Nếu có dialog mở -> disable scroll */
html:has(dialog[open]) {
  overflow: hidden;
}

Code ngắn hơn. Không event listener. Không state management. Chỉ CSS thuần.


Layout hiện đại: Grid, subgrid và aspect-ratio

CSS Grid không còn mới, nhưng subgridgrid-auto-flow dense thì vẫn bị nhiều người bỏ qua. Kết hợp với aspect-ratio, bạn có thể xây dựng layout phức tạp mà không cần tính toán tay.

Subgrid - khi children cần align với grid cha

/* Grid cha */
.article-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

/* Card kế thừa grid của cha */
.article-card {
  display: grid;
  grid-row: span 3; /* Chiếm 3 dòng */
  grid-template-rows: subgrid; /* Dùng lại rows của cha */
}

/* Giờ title, excerpt, meta của mọi card sẽ align nhau */
.article-card__title { grid-row: 1; }
.article-card__excerpt { grid-row: 2; }
.article-card__meta { grid-row: 3; }

Không cần min-height. Không cần flexbox stretch. Card tự align nhau.

grid-auto-flow: dense

/* Pinterest-style layout không cần JS */
.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-auto-flow: dense; /* Tự fill vào chỗ trống */
  gap: 1rem;
}

.masonry-grid .item--tall {
  grid-row: span 2;
}

.masonry-grid .item--wide {
  grid-column: span 2;
}

345999 Masonry-style grid bằng CSS thuần - không cần Masonry.js hay thư viện nào

aspect-ratio cho media

/* Tỉ lệ cố định cho mọi loại media */
.thumbnail {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  width: 100%;
}

.avatar {
  aspect-ratio: 1; /* Vuông hoàn hảo */
  border-radius: 50%;
}

/* Card preview tỉ lệ A4 */
.doc-preview {
  aspect-ratio: 210 / 297;
}

Trước khi có aspect-ratio, mình phải dùng padding-bottom hack (padding-bottom: 56.25% cho 16:9). Giờ thì không cần nhớ công thức đó nữa.


Responsive typography không cần breakpoint

Typography responsive là thứ tốn thời gian debug nhất. Trước đây cách thông thường là:

/* Cách cũ - nhiều breakpoints, nhiều override */
h1 { font-size: 2rem; }
@media (min-width: 768px) { h1 { font-size: 2.5rem; } }
@media (min-width: 1200px) { h1 { font-size: 3rem; } }

Ba breakpoints chỉ để scale font. Nhân với số heading levels và font properties khác - bạn có một mớ CSS khó maintain.

Giải pháp hiện đại là clamp() kết hợp với viewport units:

/* Min 1.5rem, max 3rem, tự scale mượt mà */
h1 {
  font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}

h2 {
  font-size: clamp(1.25rem, 3vw + 0.75rem, 2.25rem);
}

p {
  font-size: clamp(1rem, 1.5vw + 0.5rem, 1.125rem);
  line-height: clamp(1.5, 1.5 + 0.5vw, 1.75);
}

/* Spacing cũng responsive */
.section {
  padding: clamp(2rem, 5vw, 5rem) clamp(1rem, 3vw, 2rem);
}

346000 clamp() thay thế hàng chục breakpoints bằng một dòng CSS

Công thức tính giá trị giữa của clamp() cho fluid typography:

Font scale = preferred-size * 100vw + offset

Ví dụ muốn font từ 16px (320px viewport) đến 24px (1200px viewport):

  • Slope = (24 - 16) / (1200 - 320) = 0.00909 → xấp xỉ 0.91vw
  • Offset = 16 - 0.91 * 3.2 = 13.09px ≈ 0.82rem
  • Kết quả: clamp(1rem, 0.91vw + 0.82rem, 1.5rem)

Có thể tính tay hoặc dùng tool online như Fluid Type Scale Calculator để generate nhanh hơn.