Common CSS Mistakes
Unlike the classic "bugs" (broken behavior with a specific cause), this page covers anti-patterns — code that works today but creates ongoing pain: harder maintenance, fragile layouts, and CSS nobody wants to touch. Each one comes from a reasonable-seeming shortcut that stops paying off at scale.
1. Overusing !important
.button {
color: blue !important;
}
/* Six months later, someone needs a red variant... */
.button.danger {
color: red !important; /* had to match the !important just to win */
}!important does not just win a specificity fight — it exits the normal cascade entirely, so the only way to ever override it again is another !important with equal or higher specificity. Codebases that reach for it casually end up in an !important arms race where nothing can be safely adjusted without more of it.Instead of | Do this |
|---|---|
| Fix the actual specificity conflict, or reorder source so the correct rule wins naturally |
| Only acceptable there, since utilities are meant to always win — document this exception clearly |
2. Deep, over-specific selectors
/* Fragile: breaks the moment the markup structure changes */
.page .sidebar .widget ul li a.active {
color: red;
}
/* Robust: one class, targets the thing directly */
.widget-link--active {
color: red;
}<div> and the rule silently stops matching. They also rack up specificity, making the selector harder to override later without escalating the specificity war further.3. Magic numbers
/* What is 37px? Why 37? */
.dropdown {
top: 37px;
}
/* Better: derive it from something meaningful, or name it */
.dropdown {
top: calc(var(--header-height) + var(--spacing-sm));
}A magic number is any value with no explanation for why that specific number was chosen
They tend to be discovered by trial and error to "look right" in one browser, one viewport, one moment in time
They break silently the next time something nearby changes size
Fix: derive the value from a variable, a calculation, or at minimum leave a comment explaining the reasoning
4. Absolute positioning used for layout
/* Anti-pattern: positioning everything by hand */
.header { position: absolute; top: 0; left: 0; width: 100%; }
.sidebar { position: absolute; top: 60px; left: 0; width: 200px; }
.content { position: absolute; top: 60px; left: 200px; right: 0; }.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr;
}
/* Elements naturally push each other, no coordinates to maintain */5. Fixed pixel widths everywhere
/* Breaks on any screen narrower than 960px */
.container {
width: 960px;
}.container {
width: 100%;
max-width: 960px; /* caps on large screens, shrinks freely on small ones */
margin-inline: auto;
padding-inline: 16px;
}6. Not using custom properties
/* The same brand color, copy-pasted forty times across the codebase */
.button { background: #2563eb; }
.link { color: #2563eb; }
.badge { border-color: #2563eb; }
/* Rebrand day: forty places to find and change, guaranteed to miss one */:root {
--color-brand: #2563eb;
}
.button { background: var(--color-brand); }
.link { color: var(--color-brand); }
.badge { border-color: var(--color-brand); }
/* Rebrand day: one line to change */7. Resetting everything with a giant reset
/* Overkill: strips useful semantics along with the unwanted defaults */
* {
all: unset;
}all: unset (or an outdated Eric Meyer-style full reset) strips out genuinely useful browser defaults too — list markers, focus rings, form control affordances — that then have to be manually rebuilt from scratch, often imperfectly, at a real accessibility cost./* Prefer a minimal, deliberate reset (roughly Josh Comeau's / modern
normalize style) that removes surprises without deleting useful defaults */
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}Summary table
Anti-pattern | Cost | Better approach |
|---|---|---|
Overusing | Cascade becomes unpredictable, escalates over time | Fix specificity/order instead; reserve for true utility overrides |
Deep selectors | Tightly coupled to markup structure, fragile | Flat, class-based selectors |
Magic numbers | Unexplained values that silently break | Derive from variables/calc, or comment the reasoning |
Absolute positioning for layout | Overlaps on any content-size change | Flexbox/Grid for multi-element layout |
Fixed pixel widths | Breaks on smaller viewports |
|
No custom properties | No single source of truth for repeated values | Tokenize colors/spacing/type with |
Blanket resets | Deletes useful, accessible defaults | Minimal, deliberate reset |
8. Ignoring the default focus outline
/* Anti-pattern: removing focus styles without replacing them */
button:focus {
outline: none;
}/* Better: keep it, or replace it with an equally visible alternative,
and only for pointer users who don't need it */
button:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}9. Mixing units inconsistently
/* A grab-bag of units with no underlying system */
.card {
padding: 15px;
margin-bottom: 1.3em;
font-size: 18px;
border-radius: 0.6rem;
}px, em, and rem with no consistent rule makes spacing unpredictable when a user changes their base font size, and makes it hard to keep values in step with a design system's actual scale./* Consistent: rem for anything that should scale with user font-size
preferences, px only for things that genuinely should not scale
(like a 1px hairline border) */
.card {
padding: 1rem;
margin-bottom: 1.25rem;
font-size: 1.125rem;
border-radius: 0.5rem;
border: 1px solid #e5e7eb;
}10. Forgetting print styles entirely
Many sites never define a single @media print rule, which means navigation bars, ads, and interactive controls all print exactly as they appear on screen — often wasting paper and looking unprofessional on anything a user actually prints (receipts, articles, directions).
@media print {
nav, .ads, .no-print {
display: none;
}
body {
color: black;
background: white;
}
a[href]::after {
content: " (" attr(href) ")"; /* show URLs since links aren't clickable on paper */
}
}Summary table
Anti-pattern | Cost | Better approach |
|---|---|---|
Overusing | Cascade becomes unpredictable, escalates over time | Fix specificity/order instead; reserve for true utility overrides |
Deep selectors | Tightly coupled to markup structure, fragile | Flat, class-based selectors |
Magic numbers | Unexplained values that silently break | Derive from variables/calc, or comment the reasoning |
Absolute positioning for layout | Overlaps on any content-size change | Flexbox/Grid for multi-element layout |
Fixed pixel widths | Breaks on smaller viewports |
|
No custom properties | No single source of truth for repeated values | Tokenize colors/spacing/type with |
Blanket resets | Deletes useful, accessible defaults | Minimal, deliberate reset |
Removing focus outlines | Breaks keyboard navigation entirely |
|
Inconsistent units | Unpredictable scaling, hard to maintain a design scale |
|
No print styles | Unusable, unprofessional printed output | A minimal |