Tailwind CSS 4: OKLCH Colors, Arbitrary Values Và Workflow Thiết Kế Nhanh Hơn

5 tháng trước · 7 phút đọc
Tailwind CSS 4 thay đổi gì so với trước?
Mình nhớ hồi dùng Tailwind v3, mỗi lần cần màu brand riêng là phải mở tailwind.config.js, thêm hex vào extend.colors, save, rồi mới quay lại component dùng. Không nhiều bước, nhưng đủ để phá vỡ flow.
Tailwind CSS 4 giải quyết điều này theo hướng khác: thay vì config file JavaScript, giờ bạn khai báo design token ngay trong CSS qua @theme. Toàn bộ hệ thống màu mặc định cũng được viết lại bằng OKLCH thay vì hex hay HSL cũ.
Từ config JS sang CSS-native - ít bước chuyển ngữ cảnh hơn
Ngoài màu sắc, v4 còn bổ sung palette mới như mauve, olive, mist, taupe - tất cả đều được định nghĩa trong OKLCH. Đây không phải thay đổi cosmetic; nó ảnh hưởng trực tiếp đến cách bạn xây design system và tốc độ iteration.
Arbitrary values vẫn còn đó, và workflow tổng thể vẫn là utility-first. Nhưng cách tổ chức theme đã CSS-native hơn nhiều so với trước.
OKLCH là gì và tại sao Tailwind chọn nó?
OKLCH là không gian màu (color space) được thiết kế để perceptually uniform - tức là khi bạn thay đổi giá trị lightness hay chroma theo từng bước đều, mắt người cũng cảm nhận sự thay đổi đó đều nhau. Với RGB hay HSL, điều này không đảm bảo.
OKLCH có 4 thành phần:
- L (lightness): độ sáng, từ 0 (đen) đến 1 (trắng)
- C (chroma): độ bão hòa màu
- H (hue): góc màu sắc (0-360 độ)
- Alpha (tùy chọn): độ trong suốt
L, C, H - 3 trục đủ để diễn đạt mọi màu một cách có hệ thống
Vấn đề với HSL? Màu hsl(200, 100%, 50%) và hsl(60, 100%, 50%) có cùng lightness trên giấy, nhưng nhìn thực tế vàng trông sáng hơn nhiều. OKLCH không bị lỗi đó.
Ví dụ so sánh thực tế:
| Mục tiêu | Cách cũ | Tailwind 4 / OKLCH |
|---|---|---|
| Palette brand | Hex #3b82f6, #2563eb, #1d4ed8 |
Token --color-brand-500: oklch(0.64 0.18 260) |
| Hover tối hơn | Tự chọn hex khác | Giảm L xuống 0.05-0.1, chroma giữ nguyên |
| Neutral ramp | Gray các bước trông không đều | Neutral scale mượt hơn trong OKLCH |
Kết quả thực tế: palette của bạn trông cố ý hơn, không phải ngẫu nhiên.
Dùng OKLCH trong Tailwind 4 như thế nào?
Trong Tailwind 4, bạn khai báo màu bằng CSS custom properties bên trong @theme. Những token này tự động trở thành utilities như text-*, bg-*, border-*.
Sau khi khai báo, dùng ngay trong markup:
Token một chỗ, dùng khắp nơi - không còn lục lọc hex trong nhiều file
Lợi ích rõ nhất: khi designer muốn đổi màu brand, bạn chỉ sửa một dòng --color-brand-500 trong @theme. Mọi chỗ dùng token đó tự cập nhật. Không cần find-replace, không sợ sót.
Một tip nhỏ: nếu bạn muốn tìm hiểu sâu hơn về CSS, khóa HTML CSS từ Zero đến Hero ở F8 cover chi tiết cách tổ chức CSS trước khi bước vào Tailwind - nền tảng này khá quan trọng để hiểu @theme không bị mơ hồ.
Arbitrary values: dùng đúng chỗ mới thấy lợi
Arbitrary values là cách Tailwind cho phép bạn dùng giá trị ngoài scale mặc định bằng cú pháp [value]. Ví dụ:
Arbitrary values - escape hatch khi scale mặc định không đủ
Vấn đề với nhiều dev mới học Tailwind là dùng arbitrary values quá nhiều hoặc quá ít:
Dùng quá nhiều: Class string đầy [value] khắp nơi → khó maintain, mất đi lợi ích của design system nhất quán.
Dùng quá ít: Mỗi giá trị ngoài scale là phải mở config, thêm token → chậm, và nhiều token chỉ dùng một lần.
Rule of thumb mình hay dùng:
- Thử nghiệm và một lần: dùng arbitrary value trực tiếp
- Lặp lại 3+ lần: promote lên
@themetoken - Theo spec design chính xác: arbitrary value, sau đó hỏi lại designer có muốn standardize không
Tailwind 4 còn cho phép arbitrary values với OKLCH trực tiếp như bg-[oklch(0.64_0.18_260)] - chú ý dùng underscore thay space trong cú pháp class.
Workflow xây UI nhanh: từ markup đến component
Tailwind 4 giữ nguyên triết lý design-as-you-build: bạn ra quyết định về màu, spacing, typography ngay lúc viết markup, không phải sau đó trong một file CSS riêng. Với @theme tokens và OKLCH, workflow này càng thêm nhất quán.
Mình thường follow thứ tự này khi viết class:
- Layout trước:
flex,grid,relative,absolute - Box model:
w-*,h-*,p-*,m-*,gap-* - Visual:
bg-*,border-*,rounded-*,shadow-* - Typography:
text-*,font-*,leading-* - State variants:
hover:,focus:,disabled: - Responsive:
sm:,md:,lg:
Class theo thứ tự concern - đọc lại sau 2 tuần vẫn hiểu
Thứ tự này không bắt buộc bởi bất kỳ plugin hay rule nào, nhưng khi cả team theo cùng convention thì code review sẽ dễ hơn rất nhiều. Bạn đọc class từ trên xuống và hiểu ngay component đó làm gì về mặt layout, trước khi quan tâm đến màu sắc hay typography.
Nếu bạn đang bắt đầu với Tailwind, khóa Tailwind CSS miễn phí tại F8 có phần thực hành xây layout thực tế khá hữu ích cho giai đoạn này.
Khi nào nên tách component thay vì dùng class thẳng?
Một trong những câu hỏi mình hay thấy nhất: "Class dài quá, có nên tách ra không?"
Câu trả lời: đừng tách sớm.
Nếu component đó đang thay đổi liên tục, hoặc chỉ dùng một lần trong app, giữ utilities thẳng trong markup sẽ nhanh hơn nhiều. Abstraction quá sớm tạo ra những component "generic" không đủ linh hoạt, rồi bạn lại phải thêm props để customize - phức tạp hơn ban đầu.
Tách component khi pattern lặp lại - không phải khi class dài
Mình dùng 3 nguyên tắc này để quyết định:
Giữ utilities thẳng khi:
- Component chỉ xuất hiện 1-2 lần
- Design đang thay đổi, chưa ổn định
- Đang ở giai đoạn prototype/thử nghiệm
Tách thành component khi:
- Pattern lặp 3+ lần trong codebase
- Component có nhiều states rõ ràng:
loading,disabled,error,selected - Class string dài đến mức đọc component chính bị xao nhãng
Promote lên @theme token khi:
- Cùng màu/spacing xuất hiện ở nhiều component không liên quan
- Giá trị có ý nghĩa trong design system (màu brand, spacing chuẩn)
Ví dụ thực tế: button component. Ban đầu bạn có thể viết thẳng:
Khi button đó xuất hiện ở 10+ chỗ với variants (primary, secondary, ghost), đó là lúc wrap lại thành <Button variant="primary">. Không phải trước đó.
Workflow này - utilities → token → component - là cách Tailwind được dùng hiệu quả nhất trong dự án thực tế.
