~/TechPurAI
~/tutorials/css-from-scratch/common-css-mistakes
intermediate·part 21 of 22·7 min read

Common CSS mistakes

Updated Aug 31, 2026CSS

Every part in this series flagged one mistake in its own Callout, styling Bright Leaf Coffee's real site. This part collects all 13 in one place, each with the CSS that causes it and the CSS that fixes it.

Mistake 1: forgetting box-sizing: border-box (part 4)

css
.card {
  width: 300px;
  padding: 20px;
  border: 1px solid #ddd;
}
/* renders at 300 + 40 (padding) + 2 (border) = 342px, not 300px */
css
*, *::before, *::after {
  box-sizing: border-box;
}

.card {
  width: 300px;
  padding: 20px;
  border: 1px solid #ddd;
}
/* renders at exactly 300px — padding and border are included in the width */

Cost: without border-box, every width calculation involving padding or border needs manual math, and a declared width silently doesn't match the rendered one.

Mistake 2: reaching for !important before checking real specificity (part 3)

css
.button { background: var(--accent); }
.card .button { background: gray; } /* wins unexpectedly — higher specificity */
.button { background: var(--accent) !important; } /* forces it, doesn't fix why */
css
.button { background: var(--accent); }
.button--muted { background: gray; } /* a specific, intentional class instead */

Cost: !important compounds into a harder problem the next time that same rule needs overriding — the actual specificity conflict is still there, just buried one layer deeper for whoever touches this next.

Mistake 3: hardcoded colors instead of design tokens (part 5, part 19)

css
.header { background: #1a5c38; }
.button-primary { background: #1a5c38; }
.footer-link:hover { color: #1a5c38; }
css
:root { --accent: #1a5c38; }
.header { background: var(--accent); }
.button-primary { background: var(--accent); }
.footer-link:hover { color: var(--accent); }

Cost: a sitewide color change with hardcoded hex values means hunting down every instance individually, and retrofitting dark mode later becomes a major undertaking instead of redefining a handful of custom properties.

Mistake 4: using display: none to hide keyword-stuffed content from visitors while leaving it for crawlers (part 8)

css
.seo-keywords { display: none; }
html
<div class="seo-keywords">best coffee shop near me cheap coffee beans discount espresso...</div>

There's no "fixed" version of this one — it's a documented black-hat SEO technique modern search engines actively detect and penalize, not a pattern with a safer variant. If content matters enough to include, it needs to be genuinely visible to visitors; if it doesn't, it shouldn't be on the page at all.

Mistake 5: setting only width on a flex item instead of flex-basis (part 10)

css
.subscription-card { width: 300px; }
/* Flexbox's own sizing algorithm can override a plain width */
css
.subscription-card { flex: 0 1 300px; } /* explicit grow, shrink, and basis */

Cost: a plain width on a flex child competes with flex-grow/flex-shrink's own sizing behavior and can be overridden by it — flex-basis (via the flex shorthand) is what Flexbox's sizing algorithm actually reads first.

Mistake 6: an absolutely positioned element with no positioned ancestor (part 13)

css
.card { /* no position declared */ }
.card .badge { position: absolute; top: 8px; right: 8px; } /* jumps to the whole page */
css
.card { position: relative; }
.card .badge { position: absolute; top: 8px; right: 8px; } /* now relative to .card */

Cost: an absolutely positioned element with no positioned ancestor references the entire page as its containing block instead of the intended local container — top/right end up measured from the wrong corner entirely.

Mistake 7: desktop-first media queries instead of mobile-first (part 14)

css
.grid { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .grid { grid-template-columns: 1fr; } }
css
.grid { grid-template-columns: 1fr; }
@media (min-width: 480px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 768px) { .grid { grid-template-columns: repeat(4, 1fr); } }

Cost: starting from the desktop layout means every smaller breakpoint has to fight and undo desktop-specific assumptions — starting mobile-first means each larger breakpoint only ever adds complexity, never has to subtract it.

Mistake 8: removing form focus outlines with no visible replacement (part 15, part 17)

css
input:focus { outline: none; }
css
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

Cost: removing the outline with nothing in its place makes keyboard navigation genuinely unusable, with zero visual indication of which field currently has focus.

Mistake 9: animating margin/top/height instead of transform/opacity (part 16, part 18)

css
.dropdown { top: -20px; transition: top 0.2s; }
.dropdown.open { top: 0; }
css
.dropdown {
  transform: translateY(-20px);
  opacity: 0;
  transition: transform 0.2s, opacity 0.2s;
}
.dropdown.open {
  transform: translateY(0);
  opacity: 1;
}

Cost: animating top triggers a real layout recalculation on every single frame of the animation — transform and opacity are handled by the compositor without touching layout at all, which directly shows up as a worse measured Interaction to Next Paint score.

Mistake 10: ignoring prefers-reduced-motion (part 16)

css
.dropdown { transition: transform 0.2s, opacity 0.2s; }
css
.dropdown { transition: transform 0.2s, opacity 0.2s; }

@media (prefers-reduced-motion: reduce) {
  .dropdown { transition: none; }
}

Cost: ignoring this forces motion on visitors who explicitly told their operating system they don't want it — a real, easily avoidable accessibility failure that costs one media query to fix.

Mistake 11: generated ::before/::after content that isn't purely decorative (part 17)

css
.required::after { content: " (required)"; }
css
.required::after { content: " *"; color: red; } /* purely decorative marker */
html
<label>Email <span class="required">*</span> <span class="sr-only">(required)</span></label>

Cost: content generated purely via CSS may not be reliably announced by every screen reader — anything a visitor genuinely needs to know belongs in real HTML, with CSS reserved for content that's decoration alone.

Mistake 12: no reserved space for images or custom fonts (part 18)

html
<img src="hero.jpg" alt="Bright Leaf Coffee's roastery">
html
<img src="hero.jpg" alt="Bright Leaf Coffee's roastery" width="1200" height="600">
css
@font-face {
  font-family: 'Brand';
  src: url('brand.woff2') format('woff2');
  font-display: swap;
}

Cost: an image with no width/height reserves zero space before it loads, so the page jumps the moment it arrives — this directly causes measured Cumulative Layout Shift, a real Core Web Vitals metric with a real ranking and UX impact. font-display: swap avoids the equivalent problem for custom fonts by showing a fallback font immediately rather than blocking text entirely.

Mistake 13: deeply nested selectors chasing specificity instead of flat, class-based rules (part 20)

css
.page .content .card .header .title { color: var(--ink); }
css
.card-title { color: var(--ink); }

Cost: deep nesting chasing specificity turns into an escalating war the next time a rule needs to win against this one — a flat, single class is both easier to override intentionally and easier to read six months later.

The thread connecting all thirteen

Every mistake above shares the same root cause as the ones catalogued in the HTML series' own roundup: a choice that looks visually fine in the one specific case being tested, while quietly breaking accessibility, performance, or maintainability in a way that's invisible until it's specifically checked for.

Common mistake

Treating this list as a one-time reference rather than a genuine, recurring pre-launch check. A pattern learned once in part 4 (box-sizing) is easy to forget by the time a new component gets built weeks later — worth an explicit check against this list before any new CSS ships.

FAQ

Which of these has the biggest performance impact? Mistake 9 (animating layout properties) and Mistake 12 (no reserved space for images) are the two most directly tied to Core Web Vitals — both show up as measurable regressions in Interaction to Next Paint and Cumulative Layout Shift respectively.

Is !important ever the right call? Rarely, and almost never in your own stylesheet — it's more defensible when overriding a third-party stylesheet you don't control and can't otherwise reach with higher specificity.

How do I actually check specificity instead of guessing? Browser DevTools shows every matching rule for an element in specificity order, with overridden rules struck through — that's a more reliable read than mentally calculating specificity scores.

Does mobile-first mean the mobile styles have to come first in the file? Yes, in the sense that matters — unprefixed rules (no media query) should be the base/mobile styles, with min-width queries adding complexity for larger screens. The literal order in the file follows from that.

Next, and last: the capstone — bringing every part of this series together into Bright Leaf Coffee's complete, styled site.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous20. CSS architecture: organizing a real stylesheet at scalenext →22. The capstone: Bright Leaf Coffee's complete, styled site