CSS

CSS Pro Tips

A collection of practical tips for writing CSS that is fast, clean, and bug-free — with Bad ↔ Good comparisons.

💡How to read: each tip has a Bad pattern (the old way) ↔ Good (the modern way). Once learned, you can apply it right away.

0. A solid reset from the start

Eliminate inconsistencies between browsers, laying the foundation for every tip below.

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; }
💡Understand the box model first → when learning Tailwind, utilities like p-*, m-*, border-* will feel very natural.

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; } /* Or a single line with grid */ .center { display: grid; place-items: center; }

2. Two-dimensional layout — Grid > table / nested flex

Bad
css
/* Using <table> or deeply nested flex to split into 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 a <div> in the viewport — single block

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

4. Responsive width — clamp() instead of 3 media queries

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() works great for font-size, padding, gap This is the “responsive without media queries” approach.

5. Aspect ratio — one line instead of the 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 with custom properties

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

To toggle dark mode, just add the class .dark on <html> — no need to change each selector.

8. Logical properties — RTL friendly

css
/* Instead of margin-left/right (physical) */ margin-inline: auto; /* horizontal, follows writing direction */ padding-block: 1rem; /* vertical, follows writing direction */ border-inline-start: 2px solid;

Automatically flips when dir="rtl" — no need to write separate CSS.

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() for automatic numbering

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 — no manual numbering needed --> <h1>Awesome</h1> <h1>Cool</h1> <h1>Radical</h1>

11. :focus-within — catch focus of a descendant

Bad
css
.dropdown { opacity: 0; visibility: hidden; } button:focus .dropdown { /* only catches focus on the button itself */ opacity: 1; visibility: visible; }
Good
css
.dropdown { opacity: 0; visibility: hidden; } /* Catch focus on ANY child inside the button */ button:focus-within .dropdown { opacity: 1; visibility: visible; }

12. :has() — parent selector (now widely supported)

css
/* If a card has an image, reduce its padding */ .card:has(img) { padding: 0.5rem; } /* If a form has an invalid input, dim the submit button */ form:has(input:invalid) button { opacity: 0.5; } /* If the body has an open sidebar, push the main content */ body:has(.sidebar.open) main { margin-left: 240px; }

13. Truncate text (single line / multiple lines)

css
/* Single line */ .truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Multiple lines */ .clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

14. Focus visible — pretty & a11y

Bad
css
button:focus { outline: none; /* breaks a11y, keyboard-tab users get lost */ }
Good
css
button:focus { outline: none; } /* Only show the outline when the user tabs with the keyboard */ button:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }
⚠️Don't disable outline without replacing it with another focus state.

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

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

16. will-change — use it in the right place

💡Only add will-change RIGHT BEFORE animating, and remove it when done. Overusing it wastes GPU memory unnecessarily.
css
/* Bad: always on for every card */ .card { will-change: transform; } /* Good: only on when it's about to animate */ .card.is-animating { will-change: transform; }

17. DevTools tip — Firefox > Chrome for debugging layout

  • Firefox has a more visual Grid Inspector & Flex Inspector than Chrome.
  • Show grid/flex lines, gaps, and item order with just one click.
  • Firefox's Font Inspector is also superior for debugging typography.
Chào bạn, mình là Nova. Có phần nào bạn muốn hiểu kỹ hơn không? Cứ nói với mình nhé.