CSS

CSS Pro Tips

Tổng hợp tips thực dụng giúp viết CSS nhanh, sạch, ít bug — kèm so sánh Bad ↔ Good.

💡Cách đọc: mỗi tip có pattern Bad (cách cũ) ↔ Good (cách hiện đại). Học xong áp dụng được ngay.

0. Reset chuẩn ngay từ đầu

Khử inconsistency giữa browser, làm tiền đề cho mọi tip phía dưới.

css
*, *::before, *::after { box-sizing: border-box; } * { margin: 0; } body { line-height: 1.5; -webkit-font-smoothing: antialiased; } img, picture, video, canvas, svg { display: block; max-width: 100%; } input, button, textarea, select { font: inherit; }
💡Hiểu box model trước → khi học Tailwind, các utility p-*, m-*, border-* sẽ trở nên rất tự nhiên.

1. Center — Flexbox > absolute hack

Bad
css
.classic { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
Good
css
.classic { display: flex; justify-content: center; align-items: center; } /* Hoặc 1 dòng với grid */ .center { display: grid; place-items: center; }

2. Layout 2 chiều — Grid > table / flex lồng

Bad
css
/* Dùng <table> hoặc flex lồng nhiều cấp để chia multi-column + multi-row */
Good
css
.layout { display: grid; grid-template-columns: 1fr 500px 1fr; grid-template-rows: 100px 200px; place-items: center; }

3. Center một <div> trong viewport — 1 block

html
<div class="parent"> <div>I'm centered</div> </div>
css
.parent { display: grid; place-content: center; height: 100vh; }

4. Responsive width — clamp() thay 3 media query

Bad
css
article { width: 50%; } @media (max-width: 600px) { article { width: 200px; } } @media (min-width: 1200px) { article { width: 800px; } }
Good
css
article { /* MIN, Preferred, MAX */ width: clamp(200px, 50%, 600px); }

clamp() hoạt động tốt cho cả font-size, padding, gap… Đây là cách “responsive không cần media query”.

5. Aspect ratio — 1 dòng thay padding-top hack

Bad
css
.container-16x9 { position: relative; padding-top: 56.25%; } .container-16x9 video { position: absolute; inset: 0; width: 100%; }
Good
css
video { width: 100%; aspect-ratio: 16 / 9; }

6. Variables for variables — CSS custom properties

Bad
css
p { color: rgb(255, 0, 0); } h1 { color: rgb(255, 0, 0); } h2 { color: rgb(255, 0, 0); }
Good
css
:root { --r: 255; --g: 0; --b: 0; --text-color: rgb(var(--r), var(--g), var(--b)); } p { --text-color: green; /* override */ color: var(--text-color); } h1 { color: var(--text-color); } h2 { color: var(--text-color); }

7. Dark mode bằng custom properties

css
:root { --bg: #fff; --fg: #111; } .dark { --bg: #111; --fg: #eee; } body { background: var(--bg); color: var(--fg); }

Toggle dark mode chỉ cần thêm class .dark lên <html> — không cần đổi từng selector.

8. Logical properties — RTL friendly

css
/* Thay vì margin-left/right (vật lý) */ margin-inline: auto; /* ngang theo writing direction */ padding-block: 1rem; /* dọc theo writing direction */ border-inline-start: 2px solid;

Tự động đảo khi dir="rtl" — không cần viết CSS riêng.

9. Stagger animation — calc() + custom property

Bad
css
.drop { animation: dropIn 1s ease forwards; } .ace { animation-delay: 100ms; } .deuce { animation-delay: 200ms; } .trey { animation-delay: 300ms; } @keyframes dropIn { from { transform: translateY(-500px); } to { transform: translateY(0); } }
Good
css
.drop { animation: dropIn 1s ease forwards; animation-delay: calc(var(--order) * 100ms); } @keyframes dropIn { from { transform: translateY(-500px); } to { transform: translateY(0); } } /* HTML */ /* <i class="drop" style="--order: 1">1</i> */ /* <i class="drop" style="--order: 2">2</i> */ /* <i class="drop" style="--order: 3">3</i> */

10. State — counter() đánh số tự động

Bad
html
<h1>1. Awesome</h1> <h1>2. Cool</h1> <h1>3. Radical</h1>
Good
html
/* CSS */ :root { counter-reset: headings; } h1 { counter-increment: headings; } h1::before { content: counter(headings) ". "; } <!-- HTML — không cần đánh số --> <h1>Awesome</h1> <h1>Cool</h1> <h1>Radical</h1>

11. :focus-within — bắt focus của descendant

Bad
css
.dropdown { opacity: 0; visibility: hidden; } button:focus .dropdown { /* chỉ bắt focus của chính button */ opacity: 1; visibility: visible; }
Good
css
.dropdown { opacity: 0; visibility: hidden; } /* Bắt focus ở BẤT KỲ con nào trong button */ button:focus-within .dropdown { opacity: 1; visibility: visible; }

12. :has() — parent selector (đã hỗ trợ rộng rãi)

css
/* Card có ảnh thì giảm padding */ .card:has(img) { padding: 0.5rem; } /* Form có input invalid thì mờ button submit */ form:has(input:invalid) button { opacity: 0.5; } /* Body có sidebar mở thì đẩy main */ body:has(.sidebar.open) main { margin-left: 240px; }

13. Truncate text (1 dòng / nhiều dòng)

css
/* 1 dòng */ .truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Nhiều dòng */ .clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

14. Focus visible — đẹp & a11y

Bad
css
button:focus { outline: none; /* phá a11y, người dùng tab keyboard sẽ lạc */ }
Good
css
button:focus { outline: none; } /* Chỉ hiện outline khi user tab bằng keyboard */ button:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }
⚠️Đừng tắt outline mà không thay bằng focus state khác.

15. Smooth scroll & scroll-margin (sticky header)

css
html { scroll-behavior: smooth; } section { scroll-margin-top: 5rem; } /* offset cho sticky header */

16. will-change — dùng đúng chỗ

💡Chỉ thêm will-change NGAY TRƯỚC khi animate, gỡ khi xong. Lạm dụng sẽ tốn GPU memory không cần thiết.
css
/* Bad: luôn bật cho mọi card */ .card { will-change: transform; } /* Good: chỉ bật khi sắp animate */ .card.is-animating { will-change: transform; }

17. DevTools tip — Firefox > Chrome khi debug layout

  • Firefox có Grid Inspector & Flex Inspector trực quan hơn Chrome.
  • Hiện grid/flex line, gap, item order chỉ với 1 click.
  • Font Inspector của Firefox cũng vượt trội cho debug typography.