Tailwind, Radix & shadcn/ui · Part 2 — Core Utilities & Layout
The utilities you reach for every day: spacing, sizing, typography and color, then the two layout engines — Flexbox and Grid — the Tailwind way, plus the mobile-first responsive system. With a live flex/grid builder.
Phần 1 đã cho bạn mô hình tư duy và một setup v4 chạy được. Giờ ta xây vốn từ. Đây là 80% Tailwind bạn dùng 80% thời gian — spacing, sizing, typography, màu, và hai cỗ máy layout (Flexbox & Grid). Nắm chắc phần này là bạn dựng được gần như mọi UI tĩnh mà không cần mở docs. Mục tiêu của bài: sau khi đọc xong, mỗi utility bạn gặp đều “đọc” được ngay ra dòng CSS nó sinh ra.
Cách học bài này như một frontend engineer
Đừng học Tailwind bằng cách cố nhớ 500 class. Hãy học theo ý định layout:
| Bạn muốn làm gì | Nhóm utility cần nghĩ tới trước |
|---|---|
| Tạo nhịp, khoảng thở | p-*, m-*, gap-*, space-y-* |
| Khống chế kích thước | w-*, h-*, min-*, max-*, size-* |
| Làm chữ dễ đọc | text-*, font-*, leading-*, tracking-*, max-w-prose |
| Sắp hàng một chiều | flex, items-*, justify-*, gap-*, min-w-0 |
| Sắp lưới hai chiều | grid, grid-cols-*, col-span-*, auto-fit/minmax |
| Thích ứng màn hình | class mặc định + sm:/md:/lg: |
| Component tự thích ứng theo nơi đặt | @container + @md:/@lg: |
Khi debug layout, hãy hỏi theo thứ tự này:
- Cha đang là layout engine gì?
block,flex, haygrid? - Trục chính là trục nào?
flex-rowthì ngang,flex-colthì dọc. - Item có được phép co không? Nếu text dài phá layout, thử
min-w-0. - Breakpoint đang áp từ đâu?
md:là từ48remtrở lên, không phải chỉ tablet. - Khoảng cách đến từ đâu?
gap, margin của con, hayspace-ysinh margin tự động?
Đây là checklist nhỏ nhưng cứu bạn khỏi 80% bug layout trong app thật.
1. Thang spacing — nhịp của toàn UI
Gần như mọi utility liên quan tới khoảng cách (padding, margin, gap, cả width/height ở dạng số) đều rút từ một thang số chung, với quy luật duy nhất:
n→n × 0.25rem(và0.25rem = 4pxởfont-sizegốc mặc định 16px). Nên muốn ra pixel, cứ nhân 4:p-4 → 16px,p-6 → 24px,p-8 → 32px.
Đây là bảng tra đầy đủ những bước hay gặp (thang còn tiếp tục lên 64, 72, 80, 96):
| Class | rem | px | Class | rem | px |
|---|---|---|---|---|---|
p-0 | 0 | 0 | p-8 | 2rem | 32 |
p-px | 1px | 1 | p-10 | 2.5rem | 40 |
p-0.5 | 0.125rem | 2 | p-12 | 3rem | 48 |
p-1 | 0.25rem | 4 | p-14 | 3.5rem | 56 |
p-1.5 | 0.375rem | 6 | p-16 | 4rem | 64 |
p-2 | 0.5rem | 8 | p-20 | 5rem | 80 |
p-2.5 | 0.625rem | 10 | p-24 | 6rem | 96 |
p-3 | 0.75rem | 12 | p-32 | 8rem | 128 |
p-4 | 1rem | 16 | p-40 | 10rem | 160 |
p-5 | 1.25rem | 20 | p-48 | 12rem | 192 |
p-6 | 1.5rem | 24 | p-56 | 14rem | 224 |
p-7 | 1.75rem | 28 | p-64 | 16rem | 256 |
Thang này áp cho mọi tiền tố spacing. Đây là toàn bộ họ utility dùng chung thang đó:
| Nhóm | Tiền tố | Sinh ra CSS |
|---|---|---|
| Padding mọi cạnh | p-* | padding |
| Padding trục | px-* / py-* | padding-inline / padding-block |
| Padding cạnh | pt-* pr-* pb-* pl-* | padding-top/right/bottom/left |
| Padding logic | ps-* / pe-* | padding-inline-start/end (theo hướng đọc) |
| Margin mọi cạnh | m-* | margin |
| Margin trục | mx-* / my-* | margin-inline / margin-block |
| Margin cạnh | mt-* mr-* mb-* ml-* | margin-top/right/bottom/left |
| Margin âm | -mt-4, -mx-2 | margin-top: -1rem… (kéo đè) |
| Căn giữa khối | mx-auto | margin-inline: auto |
| Gap (flex/grid) | gap-* gap-x-* gap-y-* | gap / column-gap / row-gap |
| Khoảng cách con | space-x-* / space-y-* | margin giữa các con (xem gotcha) |
<div class="px-4 py-2">padding-inline 1rem, padding-block 0.5rem</div>
<div class="mt-6 mb-2">trên 1.5rem, dưới 0.5rem</div>
<div class="mx-auto max-w-sm">căn giữa một khối có chiều rộng tối đa</div>
<div class="-mt-4">kéo lên đè 1rem (margin âm)</div>
space-x/space-y vs gap — cơ chế bên dưới
Hai thứ này trông giống nhau (“tạo khoảng giữa các con”) nhưng sinh CSS hoàn toàn khác:
gap-4đặtgap: 1remtrên cha — nhưng cha phải làflexhoặcgridthìgapmới có tác dụng.space-y-4không dùnggap. Nó sinh một selector dạng> * + *rồi thêmmargin-top: 1remcho mọi con trừ con đầu:
/* space-y-4 thực chất là: */
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
margin-top: 1rem;
}
Chốt: nếu cha đã là flex/grid → luôn ưu tiên
gap(gọn, không đụng tới margin của con, hoạt động cả hai chiều khi wrap). Chỉ dùngspace-y/space-xcho khối tĩnh (không flex/grid), ví dụ một stack đoạn văn. Cảnh báo:space-y“lỗi” khi con bị đảo thứ tự (flex-row-reverse) hoặc khi con có margin riêng.
2. Kích thước — width, height, min/max, size
Sizing trộn ba nguồn giá trị: thang spacing (số), phân số (1/2, 2/3…), và từ khóa (full, screen, min, max, fit).
| Class | CSS | Ý nghĩa |
|---|---|---|
w-64 | width: 16rem | số → n × 0.25rem |
w-1/2 | width: 50% | phân số → phần trăm |
w-1/3 w-2/3 | 33.33% 66.66% | phân số ba |
w-full | width: 100% | đầy theo cha |
w-screen | width: 100vw | đầy viewport ngang |
w-dvw / h-dvh | 100dvw / 100dvh | viewport động (mobile, trừ thanh URL) |
w-min | width: min-content | co tối đa theo nội dung |
w-max | width: max-content | giãn tối đa theo nội dung |
w-fit | width: fit-content | vừa khít nội dung |
h-screen | height: 100vh | cao đầy viewport |
size-10 | width: 2.5rem; height: 2.5rem | shorthand v4: cả w lẫn h |
min-w-0 | min-width: 0 | cho phép co trong flex (rất hay dùng) |
max-w-sm | max-width: 24rem | giới hạn rộng, lý tưởng cho card |
min-h-screen | min-height: 100vh | tối thiểu đầy màn (layout app) |
Thang max-w-* có một bộ tên riêng (không phải thang spacing) để giữ nội dung dễ đọc:
| Class | max-width | Class | max-width |
|---|---|---|---|
max-w-xs | 20rem | max-w-2xl | 42rem |
max-w-sm | 24rem | max-w-4xl | 56rem |
max-w-md | 28rem | max-w-7xl | 80rem |
max-w-lg | 32rem | max-w-full | 100% |
max-w-xl | 36rem | max-w-prose | 65ch (đẹp cho văn bản) |
<div class="w-64">16rem cố định</div>
<div class="w-full h-screen">đầy ngang, cao 100vh</div>
<div class="mx-auto max-w-prose">cột chữ ~65 ký tự — độ dài dòng lý tưởng để đọc</div>
<button class="size-10">nút vuông 2.5rem × 2.5rem</button>
Mẹo
min-w-0: một flex item mặc định cómin-width: auto, nghĩa là nó không co nhỏ hơn nội dung — đây là lý do text dài làm vỡ layout/không xuống dòng. Thêmmin-w-0(kèmtruncate) để item chịu co lại đúng cách.
3. Typography — kích thước, độ đậm, giãn chữ, giãn dòng
text-* cho kích thước. Điểm hay của Tailwind v4: mỗi cỡ chữ kèm sẵn một line-height mặc định hợp lý, nên text-lg vừa đặt font-size vừa đặt line-height cho bạn.
| Class | font-size | line-height đi kèm |
|---|---|---|
text-xs | 0.75rem (12px) | 1rem |
text-sm | 0.875rem (14px) | 1.25rem |
text-base | 1rem (16px) | 1.5rem |
text-lg | 1.125rem (18px) | 1.75rem |
text-xl | 1.25rem (20px) | 1.75rem |
text-2xl | 1.5rem (24px) | 2rem |
text-3xl | 1.875rem (30px) | 2.25rem |
text-4xl | 2.25rem (36px) | 2.5rem |
text-5xl | 3rem (48px) | 1 |
text-6xl…text-9xl | 3.75rem→8rem | 1 |
Độ đậm font-* ánh xạ thẳng sang font-weight:
| Class | weight | Class | weight |
|---|---|---|---|
font-thin | 100 | font-medium | 500 |
font-extralight | 200 | font-semibold | 600 |
font-light | 300 | font-bold | 700 |
font-normal | 400 | font-extrabold | 800 |
font-black | 900 |
Các trục chữ còn lại bạn sẽ chạm hằng ngày:
| Nhóm | Ví dụ | CSS |
|---|---|---|
| Giãn dòng (đè mặc định) | leading-none leading-tight leading-relaxed | line-height: 1 / 1.25 / 1.625 |
| Giãn chữ | tracking-tight tracking-normal tracking-wide | letter-spacing: -0.025em / 0 / 0.025em |
| Căn lề | text-left text-center text-right text-justify | text-align |
| Kiểu chữ | italic not-italic | font-style |
| Hoa/thường | uppercase lowercase capitalize normal-case | text-transform |
| Gạch chân | underline line-through no-underline | text-decoration |
| Cắt ngắn 1 dòng | truncate | overflow:hidden; text-overflow:ellipsis; white-space:nowrap |
| Cắt ngắn n dòng | line-clamp-2 line-clamp-3 | giới hạn còn n dòng rồi … |
| Font family | font-sans font-serif font-mono | font-family |
<h1 class="text-3xl font-bold tracking-tight">Tiêu đề lớn</h1>
<p class="text-base leading-relaxed text-slate-600">
Đoạn văn dễ đọc với line-height thoáng.
</p>
<p class="truncate">Dòng rất dài này sẽ bị cắt và thêm dấu ba chấm…</p>
<p class="line-clamp-2">Đoạn này tự cắt còn đúng 2 dòng dù nội dung dài bao nhiêu.</p>
4. Màu sắc — palette + bộ chỉnh độ mờ
Mỗi màu trong Tailwind có 11 sắc độ, từ 50 (nhạt nhất) tới 950 (đậm nhất). 500 là “chuẩn”. Cấu trúc tên utility luôn là thuộc-tính-tên-sắc-độ:
| Tiền tố | Sinh ra | Ví dụ |
|---|---|---|
text-* | color | text-slate-700 |
bg-* | background-color | bg-indigo-500 |
border-* | border-color | border-slate-200 |
ring-* | box-shadow (vòng focus) | ring-indigo-500 |
fill-* / stroke-* | fill / stroke (SVG) | fill-emerald-500 |
from-* via-* to-* | điểm dừng gradient | from-indigo-500 to-purple-600 |
divide-* | màu đường kẻ giữa các con | divide-slate-200 |
accent-* | accent-color (checkbox/radio) | accent-indigo-600 |
Một số họ màu có sẵn (mỗi họ đều có đủ 50→950): slate gray zinc neutral stone (xám), red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose, cùng black white transparent current.
Quy ước chọn sắc độ để UI hài hòa mà không phải nghĩ nhiều:
| Vai trò | Sắc độ thường dùng |
|---|---|
| Chữ chính trên nền sáng | 700–900 |
| Chữ phụ / mô tả | 500–600 |
| Nền nhấn (nút primary) | 500–600, hover +100 |
| Đường viền nhẹ | 200–300 |
| Nền khối nhạt (badge, panel) | 50–100 |
Bộ chỉnh độ mờ /NN — cơ chế
Thêm / và một số 0–100 vào bất kỳ utility màu để đặt độ mờ chỉ cho màu đó, không ảnh hưởng con bên trong (khác hẳn opacity-* làm mờ cả phần tử):
<p class="text-indigo-500/70">indigo ở 70% độ đục</p>
<div class="bg-black/50">lớp phủ đen 50% — overlay kinh điển</div>
<div class="border-white/10">viền trắng rất mờ trên nền tối</div>
Bên dưới, Tailwind v4 sinh ra giá trị màu dùng color-mix hoặc kênh alpha, ví dụ text-indigo-500/70 → color: color-mix(in oklab, var(--color-indigo-500) 70%, transparent). Vì mọi token màu ở v4 là biến CSS, bạn có thể tự thấy chúng trong DevTools.
/opacityvsopacity-50:bg-black/50chỉ làm trong suốt nền; chữ và icon bên trong vẫn đục.opacity-50làm mờ cả phần tử lẫn mọi thứ bên trong. Dùng/NNcho overlay; dùngopacity-*khi muốn làm mờ nguyên cụm (ví dụ trạng tháidisabled:opacity-50).
Cần một màu ngoài palette? Dùng arbitrary value: bg-[#1da1f2], text-[oklch(0.7_0.15_250)] — nhưng tiết chế, vì nó nằm ngoài thang chung.
5. Flexbox — kiểu Tailwind
Mỗi khái niệm Flexbox ánh xạ thành đúng một utility. Đây là bảng tra đầy đủ:
| CSS | Tailwind | Ghi chú |
|---|---|---|
display: flex | flex | bật flex container |
display: inline-flex | inline-flex | container inline |
flex-direction: row | flex-row | mặc định, ngang |
flex-direction: column | flex-col | dọc |
flex-direction: row-reverse | flex-row-reverse | đảo ngang |
flex-wrap: wrap | flex-wrap | cho xuống dòng |
flex-wrap: nowrap | flex-nowrap | không xuống dòng (mặc định) |
justify-content: flex-start | justify-start | dồn đầu trục chính |
justify-content: center | justify-center | giữa trục chính |
justify-content: flex-end | justify-end | dồn cuối |
justify-content: space-between | justify-between | đẩy ra hai biên |
justify-content: space-around | justify-around | đều, có khoảng hai rìa |
justify-content: space-evenly | justify-evenly | mọi khoảng bằng nhau |
align-items: stretch | items-stretch | kéo đầy trục phụ (mặc định) |
align-items: center | items-center | giữa trục phụ |
align-items: flex-start | items-start | đầu trục phụ |
align-items: baseline | items-baseline | thẳng đường baseline chữ |
align-self: center | self-center | đè cho riêng một item |
flex: 1 1 0% | flex-1 | chiếm phần còn lại, co giãn |
flex: 1 1 auto | flex-auto | co giãn theo nội dung |
flex: 0 1 auto | flex-initial | co nhưng không giãn |
flex: none | flex-none | khóa kích thước |
flex-grow: 1 / 0 | grow / grow-0 | cho/cấm giãn |
flex-shrink: 1 / 0 | shrink / shrink-0 | cho/cấm co |
order: 1 / -1 | order-1 / -order-1 | đổi thứ tự hiển thị |
Hai trục cần nhớ: justify-* luôn theo trục chính (mặc định ngang), items-* theo trục phụ (vuông góc trục chính). Khi đổi sang flex-col, trục chính thành dọc → justify-* giờ căn theo chiều dọc, items-* theo chiều ngang. Đây là nhầm lẫn kinh điển.
flex-row (mặc định): flex-col:
justify → ngang → justify → dọc ↓
items → dọc ↓ items → ngang →
Recipe phổ biến nhất trong mọi UI — một thanh ngang, logo trái, nav phải:
<header class="flex items-center justify-between gap-4 px-6 py-4">
<span class="font-semibold">Logo</span>
<nav class="flex items-center gap-4">
<a href="#">Home</a><a href="#">Docs</a><a href="#">About</a>
</nav>
</header>
flex-1+min-w-0: khi một itemflex-1chứa text dài cầntruncate, nhớ thêmmin-w-0, nếu không item từ chối co và text tràn ra ngoài (xem mục 2).
6. Grid — cho layout hai chiều
Flex giỏi một chiều; Grid giỏi hàng × cột cùng lúc. Bảng tra:
| CSS | Tailwind | Ghi chú |
|---|---|---|
display: grid | grid | bật grid container |
grid-template-columns: repeat(3, 1fr) | grid-cols-3 | 3 cột bằng nhau |
grid-template-columns: repeat(12, …) | grid-cols-12 | lưới 12 cột kinh điển |
grid-template-columns: none | grid-cols-none | bỏ template |
grid-template-rows: repeat(2, …) | grid-rows-2 | 2 hàng |
grid-column: span 2 | col-span-2 | item chiếm 2 cột |
grid-column: 1 / -1 | col-span-full | trải hết hàng |
grid-column-start: 2 | col-start-2 | bắt đầu ở cột 2 |
grid-row: span 2 | row-span-2 | item cao 2 hàng |
gap: 1rem | gap-4 | khe cả hai chiều |
column-gap / row-gap | gap-x-4 / gap-y-2 | khe theo trục |
grid-auto-flow: column | grid-flow-col | đổ item theo cột |
grid-auto-rows: minmax(0,1fr) | auto-rows-fr | hàng tự sinh đều nhau |
place-items: center | place-items-center | căn giữa cả 2 trục |
place-content: center | place-content-center | căn cả khối lưới |
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2">chiếm 2/3</div>
<div>1/3</div>
<div class="col-span-full">trải hết hàng dưới</div>
</div>
Lưới “tự lấp đầy” bằng auto-fit + minmax
Một lưới responsive không cần breakpoint — cột tự co theo chỗ trống, mỗi cột tối thiểu 16rem. CSS gốc là repeat(auto-fit, minmax(16rem, 1fr)). Tailwind không có utility sẵn cho cú pháp này, nên dùng arbitrary value (nhớ thay khoảng trắng bằng _):
<div class="grid grid-cols-[repeat(auto-fit,minmax(16rem,1fr))] gap-4">
<!-- thêm bao nhiêu card cũng tự xuống hàng, không cần md:/lg: -->
</div>
auto-fitvsauto-fill:auto-fitgộp các cột trống lại để các card hiện có giãn đầy;auto-fillgiữ các cột rỗng (card không giãn). Muốn card luôn lấp đầy chiều ngang →auto-fit.
Dựng cả hai layout ngay bên dưới — bật utility, rồi đổi viewport để xem override md: kích hoạt:
7. Responsive — mobile-first
Đây là khái niệm làm người mới vấp nhiều nhất, nên hãy thấm cho kỹ:
Utility không tiền tố áp ở MỌI cỡ màn. Tiền tố như
md:nghĩa là “từ breakpoint này trở LÊN” — nó không bao giờ trừ bớt ở dưới.
Bảng breakpoint mặc định (đều là min-width):
| Tiền tố | min-width | px | Thiết bị điển hình |
|---|---|---|---|
| (không) | — | 0 | mặc định, mobile-first |
sm: | 40rem | 640 | điện thoại ngang / màn nhỏ |
md: | 48rem | 768 | tablet |
lg: | 64rem | 1024 | laptop |
xl: | 80rem | 1280 | desktop |
2xl: | 96rem | 1536 | màn lớn |
Mỗi tiền tố sinh một khối @media (min-width: …). Ví dụ md:grid-cols-2 sinh ra:
@media (min-width: 48rem) {
.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
<!-- 1 cột trên mobile, 2 từ md, 3 từ lg -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">…</div>
Bạn thiết kế màn nhỏ nhất trước (class không tiền tố), rồi thêm override chỉ kích hoạt khi to lên. Đọc từ trái sang phải như kể chuyện: “xếp chồng trên mobile, hai cột từ tablet, ba cột từ laptop”.
Gotcha kinh điển: viết
md:flexrồi mong nó cũng có hiệu lực trên mobile. Không —md:chỉ bắt đầu từ 768px, dưới đó phần tử về mặc định (display: block). Muốn “flex ở mọi nơi, cột trên mobile, hàng từ desktop” thì viếtflex flex-col md:flex-row:flexáp toàn bộ,flex-collà mặc định nhỏ,md:flex-rowđè khi rộng.
Nhắm một khoảng hẹp: thêm tiền tố max-* để chặn trên. md:max-lg:hidden nghĩa là ẩn trong khoảng 768px ≤ w < 1024px.
Container queries — phản ứng theo cha, không phải viewport
Đôi khi một card cần đổi layout theo bề rộng vùng chứa nó, chứ không phải bề rộng màn hình (cùng một card có thể nằm trong sidebar hẹp hoặc khu nội dung rộng). Đó là việc của container queries, hỗ trợ sẵn ở Tailwind v4:
- Đánh dấu phần tử cha là container với
@container. - Trên con, dùng tiền tố
@sm:,@md:,@lg:… để phản ứng theo bề rộng cha.
| Tiền tố container | min-width của cha |
|---|---|
@sm: | 24rem (384px) |
@md: | 28rem (448px) |
@lg: | 32rem (512px) |
@xl: | 36rem (576px) |
<div class="@container">
<!-- 1 cột khi cha hẹp, 2 cột khi cha ≥ 28rem — bất kể viewport -->
<article class="grid grid-cols-1 @md:grid-cols-2 gap-4">…</article>
</div>
Khi nào dùng cái gì:
md:cho layout trang (theo viewport);@md:cho component tái sử dụng phải tự thích ứng dù được đặt ở đâu. Container query là vũ khí thật sự để viết component “đặt đâu cũng đẹp”.
8. Recipe thực tế
Ba layout này lặp lại trong gần như mọi app — học thuộc khung là xong.
Holy-grail (header / sidebar / main / footer): dùng grid với hàng và cột đặt tên kích thước, main chiếm phần còn lại.
<div class="grid min-h-screen grid-rows-[auto_1fr_auto]">
<header class="px-6 py-4 border-b">Header</header>
<div class="grid md:grid-cols-[16rem_1fr]">
<aside class="hidden md:block border-r p-4">Sidebar</aside>
<main class="p-6">Nội dung chính</main>
</div>
<footer class="px-6 py-4 border-t">Footer</footer>
</div>
grid-rows-[auto_1fr_auto]: header/footer cao theo nội dung, vùng giữa1frgiãn đầy → footer luôn dính đáy dù nội dung ngắn.md:grid-cols-[16rem_1fr]: sidebar 16rem cố định, main chiếm phần còn lại — vàhidden md:blockẩn sidebar trên mobile.
Lưới card responsive (tự lấp đầy, không breakpoint):
<div class="grid grid-cols-[repeat(auto-fit,minmax(15rem,1fr))] gap-6">
<article class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
<h3 class="text-lg font-semibold text-slate-900">Card</h3>
<p class="mt-2 text-sm text-slate-600">Mô tả ngắn.</p>
</article>
<!-- nhân bản article — lưới tự co cột theo chỗ trống -->
</div>
Header dính đỉnh (sticky) có nền mờ:
<header class="sticky top-0 z-10 flex items-center justify-between
border-b border-slate-200 bg-white/80 px-6 py-3 backdrop-blur">
<span class="font-semibold">Brand</span>
<nav class="flex items-center gap-4 text-sm">
<a href="#">Docs</a><a href="#">Pricing</a>
</nav>
</header>
sticky top-0: dính đỉnh khi cuộn (cần cha không cóoverflow: hidden).z-10: nổi trên nội dung cuộn bên dưới.bg-white/80 backdrop-blur: nền trắng mờ 80% + làm mờ phần phía sau → hiệu ứng kính.
Checklist layout production
Khi một layout trông ổn trong mockup nhưng vỡ ở dữ liệu thật, lỗi thường không nằm ở “Tailwind khó”, mà nằm ở giả định quá đẹp: tên ngắn, số lượng card vừa đủ, viewport lý tưởng. Trước khi coi một layout là xong, hãy kiểm qua các ca này:
| Câu hỏi | Class/pattern thường cần |
|---|---|
| Text dài có làm vỡ hàng không? | min-w-0, truncate, line-clamp-*, break-words |
| Card có cao lệch khi nội dung dài không? | h-full, flex flex-col, mt-auto cho action cuối |
| Grid có đẹp khi chỉ có 1-2 item không? | auto-fit thay vì ép grid-cols-4 |
| Sidebar có làm main bị tràn ngang không? | minmax(0,1fr) trong grid hoặc min-w-0 trên main |
| Header sticky có che nội dung không? | top-*, z-*, padding/scroll-margin cho anchor |
| Mobile có thao tác được không? | gap-*, size-* đủ lớn cho target bấm, không chỉ hover |
Một pattern rất đáng thuộc: hàng có avatar cố định, phần nội dung co giãn, action bên phải không bị đẩy vỡ.
<div class="flex items-center gap-3">
<img class="size-10 shrink-0 rounded-full" src="/avatar.png" alt="" />
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-slate-900">
Tên người dùng rất rất dài có thể đến từ dữ liệu thật
</p>
<p class="truncate text-sm text-slate-500">email-rat-dai@example.com</p>
</div>
<button class="shrink-0 rounded-md border px-3 py-1.5 text-sm">Mời</button>
</div>
Điểm đáng chú ý:
shrink-0giữ avatar và button không bị bóp méo.min-w-0 flex-1cho vùng text được phép co.truncatechỉ hoạt động đúng khi phần tử có bề rộng bị giới hạn, nên nó thường đi cùngmin-w-0.
9. Gotchas thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
gap-4 không tạo khoảng cách | Cha chưa là flex/grid | Thêm flex hoặc grid vào cha, hoặc dùng space-y-* cho khối thường |
space-y lệch khi đảo thứ tự | space-y dùng > * + * theo thứ tự DOM | Chuyển sang gap trên flex/grid |
md:flex không ăn trên mobile | md: chỉ áp từ 768px trở lên | Đặt mặc định không tiền tố: flex flex-col md:flex-row |
| Text dài làm vỡ flex layout | Flex item mặc định min-width: auto, không chịu co | Thêm min-w-0 (+ truncate nếu cần) |
grid-cols-4 đẩy item tràn ngang | Item con quá rộng, không co | min-w-0 trên con, hoặc giảm số cột ở mobile |
sticky top-0 không dính | Một tổ tiên có overflow: hidden/auto | Bỏ overflow ở tổ tiên, hoặc đổi chiến lược layout |
bg-black/50 làm mờ cả chữ bên trong | Nhầm với opacity-50 | Dùng /NN (chỉ màu) thay vì opacity-* (cả phần tử) |
grid-cols-[repeat(auto-fit,minmax(16rem,1fr))] không build | Có khoảng trắng trong arbitrary value | Thay mọi space bằng _: minmax(16rem,1fr) |
10. Bài tập
1. Dựng navbar: full width, item căn giữa dọc, logo bên trái và nhóm link bên phải, gap 1rem.
Lời giải
<header class="flex items-center justify-between gap-4 px-6 py-4">
<span class="font-semibold">Logo</span>
<nav class="flex items-center gap-4"><a>Home</a><a>Docs</a><a>About</a></nav>
</header>justify-between đẩy logo và nav ra hai biên; items-center căn giữa theo chiều dọc.
2. Lưới card responsive: 1 cột mobile, 2 từ sm, 3 từ lg, gap 1.5rem.
Lời giải
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">…</div>Mobile-first: grid-cols-1 là mặc định, hai tiền tố sau chỉ đè khi màn to lên.
3. Căn giữa một box cả ngang và dọc trong một section cao full màn hình.
Lời giải
<section class="flex items-center justify-center min-h-screen">
<div class="max-w-sm">Centered</div>
</section>items-center (trục phụ) + justify-center (trục chính) = căn giữa hai chiều. Dùng min-h-screen thay h-screen để box không bị cắt nếu nội dung cao hơn màn.
4. Không tra cứu: gap-10 sinh ra khoảng cách bao nhiêu pixel, và p-3.5 là bao nhiêu?
Lời giải
gap-10 = 10 × 0.25rem = 2.5rem = 40px. p-3.5 = 3.5 × 0.25rem = 0.875rem = 14px. Quy luật nhân-4 áp cho cả số lẻ .5.
5. Một flex item chứa tiêu đề rất dài cần cắt bằng truncate, nhưng text vẫn tràn ra ngoài. Thiếu class nào?
Lời giải
Thiếu min-w-0. Flex item mặc định min-width: auto nên không co nhỏ hơn nội dung; truncate chỉ có tác dụng khi item được phép hẹp lại: class="min-w-0 truncate".
6. Viết một lưới card tự co (mỗi card tối thiểu 12rem, lấp đầy hàng) không dùng bất kỳ breakpoint sm:/md:/lg: nào.
Lời giải
<div class="grid grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-4">…</div>auto-fit gộp cột trống để card hiện có giãn đầy; minmax(12rem,1fr) đặt sàn 12rem rồi cho giãn. Nhớ thay khoảng trắng bằng _ trong arbitrary value.
Nâng cao: trong builder ở mục 6, tạo layout cột xếp chồng khi hẹp và hàng khi rộng bằng override md:, rồi đọc khối @media sinh ra để xác nhận breakpoint là 48rem.
Điểm chính
- Một thang spacing (
n × 0.25rem, nhân-4 ra px) chi phối padding, margin, gap và sizing dạng số — đó là lý do UI Tailwind có nhịp nhất quán. gapcần cha là flex/grid và là lựa chọn ưu tiên;space-y/space-xdùng selector> * + *cho khối thường, nhưng dễ lỗi khi đảo thứ tự.- Sizing trộn số, phân số và từ khóa (
full,screen,min/max/fit); nhớmin-w-0để flex item chịu co, vàmax-w-prosecho độ dài dòng dễ đọc. - Typography: mỗi
text-*kèm sẵnline-heighthợp lý;truncate/line-clamp-nđể cắt gọn nội dung dài. - Màu theo cấu trúc
thuộc-tính-tên-sắc-độ(50→950); bộ chỉnh/NNlàm mờ chỉ màu đó, khácopacity-*làm mờ cả phần tử. - Flexbox cho một chiều (
justify-*theo trục chính,items-*theo trục phụ); Grid cho hai chiều, vàgrid-cols-[repeat(auto-fit,minmax(…))]cho lưới responsive không cần breakpoint. - Responsive là mobile-first: không tiền tố = mọi cỡ,
md:/lg:= breakpoint đó trở lên. Container queries (@container+@md:) cho component phản ứng theo cha — đặt đâu cũng đẹp.
Tiếp theo
Phần 3 — Design token & theming ở v4: directive @theme, tự định nghĩa màu/font/spacing dưới dạng biến CSS, cách làm dark mode kiểu v4, và cách mỗi token tự trở thành một utility.