CSS box-shadow đẹp: Soft UI stack vs một lớp đậm, elevation token và chữ dễ đọc
Card đẹp năm 2026 thường dùng soft shadow stack (nhiều lớp mỏng) chứ không phải một lớp đen đậm. Coi shadow như elevation token trong design system: mỗi cấp (raised / floating / modal) một bộ số cố định. Tránh bóng làm chữ khó đọc — đặc biệt trên card có tiêu đề tiếng Việt. Tạo và so sánh nhanh trên trình duyệt với công cụ CSS box-shadow Kawa (slider, nhiều lớp, copy CSS, không cần đăng ký).
Freelance và team outsource Việt Nam hay nhận Figma từ khách Nhật/US với ghi chú “soft shadow như Material” rồi paste một dòng 0 8px 24px rgba(0,0,0,0.45). Kết quả: UI nhìn già, card nặng, và chữ trên nền sáng bị mờ cảm nhận dù contrast hex vẫn đạt.
Bài này tập trung vào Soft UI shadow stack, elevation token, và cách giữ chữ dễ đọc — không chỉ giải thích cú pháp box-shadow.
Bài viết này giúp bạn
- Phân biệt một lớp đậm vs stack nhiều lớp mỏng
- Có bảng elevation token copy được vào CSS variables
- Biết khi nào shadow làm giảm khả năng đọc chữ
- Checklist trước khi merge UI card
- Dùng box-shadow generator để chỉnh số liệu bằng mắt, không đoán
Vì sao một lớp đậm thường xấu
box-shadow gồm: offset-x, offset-y, blur, spread, màu (thường rgba).
Ánh sáng thật hiếm khi tạo một vệt đen cứng. Một lớp với rgba(0,0,0,0.5) giống “vẽ bóng bằng bút lông” hơn là “vật thể nổi trên bề mặt”.
Ba quy tắc thực dụng:
- Opacity thấp — khoảng
0.06–0.15mỗi lớp (hiếm khi cần >0.25cho card thường). - Blur lớn hơn offset — bóng lan mềm, không cắt cạnh cứng.
- offset-y dương nhỏ — giả lập nguồn sáng phía trên.
/* ❌ Một lớp đậm — dễ nhìn “rẻ” và nặng */
.card-bad {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.45);
}
/* ✅ Soft stack — cạnh gần + chiều sâu xa */
.card-good {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.06),
0 4px 12px rgba(0, 0, 0, 0.10);
}
Mở generator box-shadow, thêm hai shadow, kéo blur/opacity — so mắt hai phiên bản trên cùng preview. Đây là cách nhanh hơn chỉnh tay trong DevTools khi đang sync với Figma.
Soft UI stack: lớp gần + lớp xa
Soft UI / card hiện đại thường tách bóng thành ít nhất hai vai trò:
| Vai trò | Đặc điểm | Mục đích |
|---|---|---|
| Contact / cạnh | Blur nhỏ, opacity hơi cao hơn một chút | Giữ mép card đọc được trên nền trắng |
| Ambient / chiều sâu | Blur lớn, opacity thấp hơn | Tạo cảm giác nổi mà không “đen” |
.card-soft {
border-radius: 12px;
background: #fff;
box-shadow:
0 1px 2px rgba(15, 23, 42, 0.06),
0 8px 24px rgba(15, 23, 42, 0.08);
}
Dùng rgba(15, 23, 42, …) (slate) thay vì đen thuần thường hòa nền xám hiện đại hơn — đặc biệt dashboard SaaS mà nhiều dự án VN làm cho khách nước ngoài.
| Tiêu chí | Một lớp đậm | Soft stack (2–3 lớp) |
|---|---|---|
| Cảm giác | Nặng, ‘drop shadow 2012’ | Nhẹ, gần Material / Soft UI |
| Độ kiểm soát | Chỉ một núm blur/opacity | Tách cạnh và chiều sâu |
| Chữ trên card | Dễ bị ‘mờ’ cảm nhận nếu opacity cao | Giữ chữ sạch nếu opacity từng lớp thấp |
| Dark mode | Đen trên đen gần như mất | Cần token riêng (thường dùng shadow sáng rất mỏng hoặc border) |
| Design system | Khó đặt tên level | Map thẳng sang elevation-1, -2… |
Elevation token: đừng để mỗi PR một số khác
Khi 3 freelancer cùng repo, bạn sẽ thấy --shadow: 0 4px 10px …, --cardShadow: …, rồi hard-code trong component. Elevation token giải quyết bằng một bảng cấp độ:
:root {
--shadow-0: none;
--shadow-1: 0 1px 2px rgba(15, 23, 42, 0.06); /* surface / list row */
--shadow-2:
0 1px 2px rgba(15, 23, 42, 0.06),
0 4px 12px rgba(15, 23, 42, 0.08); /* card raised */
--shadow-3:
0 2px 4px rgba(15, 23, 42, 0.06),
0 12px 28px rgba(15, 23, 42, 0.12); /* dropdown / popover */
--shadow-4:
0 4px 8px rgba(15, 23, 42, 0.08),
0 24px 48px rgba(15, 23, 42, 0.16); /* modal */
}
.card { box-shadow: var(--shadow-2); }
.modal { box-shadow: var(--shadow-4); }
Quy ước dùng:
- 0 — phẳng, chỉ border hoặc chia nền
- 1 — hàng trong list, chip nhẹ
- 2 — card nội dung chính
- 3 — menu, tooltip lớn, filter panel
- 4 — dialog / drawer nổi trên overlay
Khi khách bảo “card nổi hơn một chút”, bạn tăng một cấp token, không tự chế 0 7px 19px rgba(0,0,0,0.37).
Tạo từng cấp trên công cụ box-shadow: chỉnh preview → copy → dán vào :root. Nhiều lớp trong tool khớp đúng cú pháp comma-separated của CSS.
Shadow làm chữ khó đọc — lỗi hay bị bỏ qua
Shadow không đổi màu chữ trực tiếp, nhưng vẫn phá trải nghiệm đọc theo ba cách:
- Cạnh chữ bị ‘nhòe’ cảm nhận khi bóng đậm sát vùng title (đặc biệt chữ nhỏ 14px tiếng Việt có dấu).
- Card trên nền ảnh / gradient — bóng tối cộng nền tối = khối chữ nổi kém.
- Inset + text — input lõm quá sâu khiến placeholder xám càng khó đọc.
Checklist nhanh trước khi ship:
- Opacity mỗi lớp ≤
0.15với card nền trắng - Không đặt
filter: drop-shadowtrùng vớibox-shadowtrên cùng khối text - Đo contrast chữ/nền bằng tool contrast (WCAG), không tin vào “trông ổn trên MacBook”
- Thử tiêu đề mẫu:
Đăng ký nhận báo cáo tháng 7— dấu thanh/huyền dễ lộ vấn đề hơn Lorem Ipsum
/* ❌ Bóng lan vào vùng chữ + opacity cao */
.promo {
padding: 12px;
box-shadow: 0 0 40px 20px rgba(0, 0, 0, 0.35);
}
/* ✅ Shadow dưới card; chữ nằm trên nền đặc, không bị phủ */
.promo {
padding: 16px 20px;
background: #fff;
box-shadow:
0 1px 2px rgba(15, 23, 42, 0.05),
0 10px 24px rgba(15, 23, 42, 0.08);
}
Nếu cần chiều sâu trên dark mode: ưu tiên border: 1px solid rgba(255,255,255,0.08) + shadow rất mỏng sáng, thay vì copy nguyên token light mode (bóng đen gần như biến mất trên nền #0f172a).
Case study 1: Landing card giá SaaS
Tình huống: Landing tiếng Việt, 3 card Pricing. Designer Figma dùng soft shadow; junior paste một lớp 0 10px 40px rgba(0,0,0,0.4).
Hậu quả: Card giữa (highlighted) nhìn “bẩn”; review khách JP comment “shadow too strong”.
Sửa: --shadow-2 cho card thường, --shadow-3 + border accent cho card đề xuất. Thời gian chỉnh bằng generator + token: khoảng 15 phút, không cần redesign.
Case study 2: Dashboard outsource — hover nâng elevation
Tình huống: Table row hover nhảy từ không bóng sang bóng đậm → layout shift cảm nhận và chữ nhấp nháy.
Sửa: Giữ border ổn định; hover chỉ đổi từ --shadow-1 → --shadow-2 với transition: box-shadow 150ms ease. Không đổi padding khi hover.
Quy trình thực tế với Kawa
- Mở CSS box-shadow generator
- Đặt offset-y nhỏ (2–8px), blur lớn hơn offset
- Thêm lớp thứ hai mỏng hơn, blur rộng hơn
- Giảm opacity đến khi card nổi nhưng chữ title vẫn sắc
- Copy CSS → gán vào
--shadow-ntrong design tokens - So dark/light trước khi mở PR
Công cụ chạy trên trình duyệt, không bắt đăng ký — phù hợp chỉnh nhanh trong lúc họp sync Figma với khách.
Lỗi thường gặp
| Lỗi | Hệ quả | Cách tránh |
|---|---|---|
| Opacity ≥ 0.4 một lớp | UI già, nặng | Soft stack, opacity thấp |
| Chỉ chỉnh blur, quên spread | Bóng loang hoặc quá sát | Spread âm nhẹ cho card nhỏ |
| Cùng shadow cho card và modal | Không có hierarchy | Elevation 2 vs 4 |
| Copy token light sang dark | Bóng biến mất | Token riêng + border |
| Shadow trên ảnh nền chữ | Khó đọc | Nền đặc dưới text hoặc scrim |
FAQ mở rộng trong bài
Có cần shadow nếu đã có border?
Border định nghĩa cạnh; shadow định nghĩa độ cao. List dày hàng: border đủ. Card mua hàng / modal: nên có elevation.
box-shadow khác drop-shadow?
box-shadow theo hộp chữ nhật. filter: drop-shadow() theo hình alpha (icon, PNG trong suốt). Card UI hầu hết dùng box-shadow.
Neumorphism còn dùng?
Hiếm cho sản phẩm SaaS 2026. Soft stack mỏng + nền phẳng dễ maintain và dễ đọc chữ hơn neumorphism inset/outset nặng.
Liên quan
- Thử ngay: Tạo box-shadow CSS
- Cùng chủ đề UI: glassmorphism / gradient trên blog VI khi cần chiều sâu khác shadow
- Kiểm tra chữ dễ đọc: kết hợp contrast tool khi ship card quan trọng
Bookmark lý do: bảng elevation token + so sánh soft stack vs một lớp đậm — đủ để trả lời khách “làm shadow dịu lại” trong một PR gọn.