Đ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

Tailwind CSS 2026 và Modern CSS: 7 kỹ thuật làm UI nhanh hơn, sạch hơn

Ếch Trendy
Ếch Trendy

một tháng trước · 6 phút đọc

Mình đã viết CSS kiểu cũ quá lâu

Hồi 2022, mình vẫn còn viết BEM, tạo file _card.scss, lồng selector 4 tầng. Mỗi lần designer thay màu là mình phải mở 3 file khác nhau. Kết quả? Codebase sau 6 tháng không ai dám đụng vào.

Tailwind CSS thay đổi workflow đó hoàn toàn. Nhưng đến 2026, chỉ biết Tailwind thôi chưa đủ. Phiên bản 4.0 đã thay đổi khá nhiều thứ, và kết hợp với modern CSS features như container queries hay :has(), bạn có thể làm những thứ trước đây cần đến JavaScript.

Bài này mình tổng hợp 7 kỹ thuật thực tế, kèm code chạy được, để bạn dùng ngay.

346863 Từ file SCSS lồng nhau đến utility classes - workflow đã thay đổi hoàn toàn


Kỹ thuật 1: @theme - design tokens ngay trong CSS

Tailwind 4.0 không còn bắt bạn phải mở tailwind.config.js để thay màu nữa. Token giờ sống trong CSS, qua directive @theme.

/* tokens.css - nguồn sự thật duy nhất cho design system */
@theme {
  --color-brand-500: oklch(60% 0.2 250);
  --color-brand-600: oklch(52% 0.22 250);
  --spacing-section: 4rem;
  --radius-card: 0.75rem;
}

Sau đó dùng trực tiếp trong Tailwind class hoặc CSS thuần:

<!-- Dùng qua utility class -->
<button class="bg-brand-500 hover:bg-brand-600 rounded-card px-6 py-3">
  Đăng ký
</button>
/* Hoặc trong CSS thuần khi cần selector phức tạp */
.hero-section {
  padding-block: var(--spacing-section);
  background: color-mix(in oklch, var(--color-brand-500) 15%, white);
}

346864 Token khai báo một lần, dùng ở cả utility class lẫn CSS thuần - không còn drift

Lợi ích thực tế: khi designer đổi brand color, bạn chỉ sửa một dòng trong @theme. Mọi component tự cập nhật.

Một lưu ý quan trọng: đừng nhét tất cả vào @theme. Chỉ token nào thật sự thuộc design system - màu, spacing, radius, shadow, font - mới cần ở đây. Giá trị one-off vẫn nên dùng arbitrary value.


Kỹ thuật 2: Container queries - responsive theo vùng chứa, không phải viewport

Vấn đề của responsive thuần breakpoint: một card trong sidebar và card trong main content có cùng viewport width, nhưng không gian thật sự chúng nhận được khác nhau hoàn toàn.

Container queries giải quyết đúng bài toán này.

<!-- Đánh dấu container -->
<div class="@container">
  <div class="card @sm:flex @lg:grid @lg:grid-cols-2">
    <img class="@sm:w-1/3 @lg:w-full" src="..." />
    <div class="p-4">
      <h3 class="text-lg font-semibold">Tiêu đề card</h3>
      <p class="text-sm text-gray-600">Mô tả ngắn...</p>
    </div>
  </div>
</div>

Tailwind 4 có sẵn @container, @sm:, @md:, @lg: variants. Không cần plugin thêm.

346865 Cùng component, khác container - card tự điều chỉnh layout mà không cần breakpoint viewport

Khi nào nên dùng container queries thay vì breakpoints thông thường? Khi component được tái sử dụng ở nhiều layout khác nhau - card, widget dashboard, sidebar block, toolbar. Khi component chỉ xuất hiện ở một chỗ cố định, breakpoint viewport vẫn đơn giản hơn và đủ dùng.


Kỹ thuật 3: :has() selector - parent selector đã đến

Trước đây, để style một card khác đi khi nó chứa ảnh, bạn cần thêm class JS hoặc tạo component variant riêng. Với :has(), không cần nữa.

/* Card tự nhận biết mình có ảnh hay không */
.card:has(img) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

/* Form field báo lỗi khi input invalid */
.form-field:has(input:invalid:not(:placeholder-shown)) {
  --field-color: var(--color-error-500);
  
  & label {
    color: var(--field-color);
  }
  
  & .helper-text {
    display: block;
    color: var(--field-color);
  }
}

/* Nav item active khi chứa link được focus */
.nav-item:has(a:focus-visible) {
  background: var(--color-brand-50);
  border-radius: var(--radius-card);
}

346866 :has() thay thế một phần JS state management cho UI patterns đơn giản

Lưu ý thực tế: :has() rất mạnh nhưng dễ lạm dụng. Mình có quy tắc đơn giản - dùng :has() khi logic đơn giản (có/không có element), dùng JS khi state phức tạp hơn hoặc cần animate. Selector quá dài và lồng nhiều tầng sẽ làm CSS khó debug hơn là giúp ích.


Kỹ thuật 4: CSS nesting native - bỏ Sass chỉ để lồng selector

Native CSS nesting giờ được hỗ trợ trên tất cả major browsers. Điều này không có nghĩa là bạn nên lồng mọi thứ - nhưng với component CSS cục bộ, nesting giúp code đọc dễ hơn nhiều.

/* Component CSS với native nesting */
.dropdown {
  position: relative;
  
  /* Trigger button */
  & > button {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  
  /* Menu panel */
  & .dropdown-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s, visibility 0.15s;
    
    /* Menu khi dropdown open */
    .dropdown:has(button[aria-expanded='true']) & {
      opacity: 1;
      visibility: visible;
    }
  }
  
  /* Menu item states */
  & .dropdown-item {
    &:hover,
    &:focus-visible {
      background: var(--color-brand-50);
    }
    
    &[aria-current='true'] {
      font-weight: 600;
      color: var(--color-brand-600);
    }
  }
}

346867 Native nesting giảm nhu cầu Sass thuần túy - nhưng Tailwind vẫn tốt hơn cho phần lớn styling

Mình vẫn dùng Sass không? Có, nhưng ít hơn. Sass vẫn có giá trị ở mixins, functions, và @each/@for loop cho các trường hợp generate utility phức tạp. Còn nếu bạn chỉ cần Sass để lồng selector, thì giờ không cần nữa.

Nếu bạn đang học Sass từ đầu, khóa Ngôn ngữ Sass cover chi tiết khi nào Sass vẫn có giá trị so với CSS native.


Kỹ thuật 5: OKLCH colors - palette nhất quán hơn HSL

Bạn đã bao giờ tạo palette màu bằng HSL, nhưng khi xem trên màn hình thực tế, màu ở lightness 50% và 70% trông không đều nhau về độ sáng cảm nhận?

Đó là vấn đề của HSL. OKLCH giải quyết điều này bằng cách map màu sắc gần hơn với cách mắt người cảm nhận.

@theme {
  /* OKLCH: L (lightness 0-1), C (chroma), H (hue 0-360) */
  /* Bước lightness đều nhau -> palette trông nhất quán */
  --color-blue-300: oklch(0.75 0.12 250);
  --color-blue-400: oklch(0.65 0.16 250);
  --color-blue-500: oklch(0.55 0.20 250);
  --color-blue-600: oklch(0.45 0.20 250);
  --color-blue-700: oklch(0.35 0.18 250);
}

So sánh với cách cũ:

/* HSL - bước lightness đều nhưng cảm nhận không đều */
--color-blue-300: hsl(217, 91%, 70%);
--color-blue-500: hsl(217, 91%, 60%); /* Trông tối hơn 300 nhiều */
--color-blue-700: hsl(217, 91%, 35%);

346868 OKLCH tạo gradient đều hơn về mặt cảm nhận thị giác - dark mode palette dễ làm hơn nhiều

Ứng dụng thực tế nhất của OKLCH: dark mode. Với HSL, bạn thường phải điều chỉnh tay từng màu khi chuyển sang dark theme. Với OKLCH và token hóa trong @theme, bạn có thể tính toán màu dark mode một cách có hệ thống hơn.

@theme {
  /* Light mode */
  --color-surface: oklch(0.98 0.005 250);
  --color-text: oklch(0.15 0.02 250);
}

@media (prefers-color-scheme: dark) {
  @theme {
    /* Chỉ đổi L, giữ C và H -> màu nhất quán */
    --color-surface: oklch(0.12 0.015 250);
    --color-text: oklch(0.92 0.01 250);
  }
}

Trang web dùng cookie để hoạt động và đo hiệu quả quảng cáo. Xem chính sách bảo mật