CSSbeginner

Common CSS Mistakes & Solutions

Identify and fix the most common CSS mistakes: margin collapse, specificity wars, mobile viewport issues, accessibility oversights, and performance killers.

16 min readยทPublished Apr 6, 2026
cssmistakesdebuggingbest-practices

Why CSS Bugs Are Frustrating

CSS fails silently. There's no error message when a property doesn't work, no stack trace when layout breaks. A missing semicolon or misunderstood behavior can waste hours. This guide covers the most common CSS mistakes, why they happen, and how to fix them permanently.

Layout Mistakes

Mistake 1: Forgetting box-sizing: border-box

The default box-sizing: content-box means width excludes padding and border. An element with width: 100%; padding: 20px will be wider than its parent.

/* BUG */
.element {
  width: 100%;
  padding: 20px;
  border: 1px solid;
  /* Actual width = 100% + 40px + 2px = overflows */
}
Parent: [====================] 100%
Child:  [========================] 100% + 42px (OVERFLOW)

Fix: Apply border-box globally.

*, *::before, *::after {
  box-sizing: border-box;
}

This should be in every project's reset. No exceptions. Every modern CSS framework includes it.

Mistake 2: Margin Collapse Surprises

Vertical margins between adjacent siblings collapse into the larger of the two.

/* BUG: expecting 60px gap, getting 40px */
.box-a { margin-bottom: 40px; }
.box-b { margin-top: 30px; }
/* Actual gap: 40px (not 70px) */

More confusing: a child's top margin can "escape" its parent if the parent has no padding, border, or overflow.

/* BUG: margin appears above the parent, not inside it */
.parent { background: blue; }
.child  { margin-top: 40px; }
Expected:
+--blue background--+
|                    |
|  40px gap          |
|  [child content]   |
+--------------------+

Actual:

  40px gap (above parent!)
+--blue background--+
|  [child content]   |
+--------------------+

Fix: Multiple options.

/* Option 1: padding on parent */
.parent { padding-top: 1px; }

/* Option 2: display: flow-root (cleanest) */
.parent { display: flow-root; }

/* Option 3: overflow */
.parent { overflow: hidden; }

/* Option 4: use flexbox or grid (margins don't collapse) */
.parent { display: flex; flex-direction: column; }

Flexbox and grid containers never have margin collapse. This is one reason modern layouts rarely suffer from this issue.

Mistake 3: Percentage Heights Not Working

/* BUG: height: 50% does nothing */
.parent {
  /* no height specified */
}
.child {
  height: 50%;  /* 50% of what? Parent has no defined height */
}

Percentage heights require the parent to have an explicit height. The chain must go all the way up.

Fix:

/* Option 1: explicit height on parent chain */
html, body { height: 100%; }
.parent { height: 100%; }
.child { height: 50%; }

/* Option 2: use viewport units */
.child { height: 50vh; }

/* Option 3: use flexbox (no percentage needed) */
.parent { display: flex; flex-direction: column; height: 100vh; }
.child { flex: 1; }

Mistake 4: Flex Items Overflowing

By default, flex items refuse to shrink below their content size (min-width: auto). Long text or wide images will overflow the container.

/* BUG: long text overflows */
.flex-container { display: flex; }
.flex-item { flex: 1; }

/* Inside .flex-item: */
.long-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Truncation doesn't work โ€” item won't shrink */
}

Fix: Add min-width: 0 to the flex item.

.flex-item {
  flex: 1;
  min-width: 0;  /* allows shrinking below content size */
}

Same issue with grid items โ€” use minmax(0, 1fr) instead of 1fr:

/* BUG */
.grid { grid-template-columns: 1fr 1fr; }

/* FIX */
.grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

Mistake 5: Using 100vh on Mobile

On mobile browsers, 100vh includes the area behind the URL bar and navigation bar. Content gets hidden behind browser chrome.

/* BUG: hero section taller than visible area on mobile */
.hero {
  height: 100vh;
}
Mobile browser:
+--URL bar----------+
|                    |
|  Your content      |  visible area
|                    |
+--nav bar-----------+
  Hidden content      <- behind browser nav
  extends here

Fix: Use dynamic viewport units.

.hero {
  height: 100vh;   /* fallback for older browsers */
  height: 100dvh;  /* dynamic viewport height โ€” adjusts with browser chrome */
}
UnitBehavior
vhAlways includes browser chrome area
svhSmallest possible viewport (browser chrome visible)
lvhLargest possible viewport (browser chrome hidden)
dvhDynamically adjusts as browser chrome appears/disappears

Specificity Mistakes

Mistake 6: Specificity Wars

When one rule doesn't work, developers add more selectors or !important to force it:

/* Escalating specificity war */
.nav a { color: blue; }
.nav .link { color: green; }                      /* override attempt */
.sidebar .nav .link { color: red; }                /* more specific */
#sidebar .nav .link { color: purple; }             /* ID to win */
#sidebar .nav .link { color: orange !important; }  /* nuclear option */

Each override requires even more specificity. The codebase becomes unmaintainable.

Fix: Keep selectors flat and low-specificity.

/* Single class selectors โ€” all same specificity */
.nav-link { color: blue; }
.nav-link-active { color: green; }
.sidebar-nav-link { color: red; }

Or use BEM:

.nav__link { color: blue; }
.nav__link--active { color: green; }

Mistake 7: Overusing !important

!important overrides all specificity. It should be used only as a last resort.

/* BAD: using !important as default approach */
.button { background: blue !important; }
.button-danger { background: red !important; }

/* Now you need !important everywhere to override anything */

Fix: Restructure selectors instead.

/* GOOD: predictable specificity */
.button { background: blue; }
.button.button-danger { background: red; }
/* Or simply: */
.button-danger { background: red; }

Legitimate !important uses:

  • Overriding third-party CSS you can't modify.
  • Utility classes that must always win (.hidden { display: none !important; }).
  • Print styles.

Mistake 8: Styling with IDs

/* BAD: (1,0,0) specificity โ€” hard to override */
#header { background: white; }
#nav { color: black; }

/* Need another ID or !important to override */

Fix: Use classes for styling.

/* GOOD: (0,1,0) specificity โ€” easy to override */
.header { background: white; }
.nav { color: black; }

Reserve IDs for JavaScript (document.getElementById) and anchor links (#section).

Typography and Text Mistakes

Mistake 9: Not Setting Font-Size on Inputs

On iOS Safari, if an input's font-size is less than 16px, the browser automatically zooms in when the user taps the input. This is jarring and disorienting.

/* BUG: causes iOS zoom */
input {
  font-size: 14px;
}

/* FIX: minimum 16px */
input, select, textarea {
  font-size: 16px;  /* or 1rem (if root font-size is 16px) */
}

Mistake 10: Hardcoded Font Sizes

/* BAD: doesn't respect user's browser font size preference */
body { font-size: 14px; }

/* GOOD: respects user preference */
body { font-size: 1rem; }  /* 16px by default, but scales with user settings */

Users who set their browser font size to "Large" expect all sites to respect that. Using px for body text overrides their preference.

/* Use rem for font-sizes */
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
p  { font-size: 1rem; }

/* Use em for component-relative spacing */
.button { padding: 0.5em 1em; }  /* scales with button's font-size */

Mistake 11: Invisible Text on Focus

/* BUG: text appears selected/highlighted but can't be seen */
.dark-card input {
  background: #1a1a1a;
  color: #1a1a1a;  /* same as background โ€” invisible! */
}

Always test form elements in both light and dark contexts. Ensure text color contrasts with background.

Responsive Mistakes

Mistake 12: Missing Viewport Meta Tag

Without the viewport meta tag, mobile browsers render at ~980px width and zoom out:

<!-- MUST have this in <head> -->
<meta name="viewport" content="width=device-width, initial-scale=1">

Mistake 13: Fixed-Width Containers

/* BUG: overflows on screens narrower than 960px */
.container {
  width: 960px;
}

/* FIX: use max-width */
.container {
  max-width: 960px;
  width: 100%;
  margin: 0 auto;
  padding: 0 1rem;
}

Mistake 14: Horizontal Scrollbar from 100vw

100vw includes the width of the vertical scrollbar (on Windows/Linux). If the page has a scrollbar, 100vw is wider than the visible area.

/* BUG: horizontal scrollbar */
.full-width {
  width: 100vw;
}

/* FIX: use 100% instead */
.full-width {
  width: 100%;
}

/* If you need to break out of a container: */
.full-bleed {
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  /* Still may need overflow-x: hidden on body */
}

Mistake 15: Desktop Hover on Touch Devices

:hover states "stick" on touch devices after tapping. The element stays highlighted until the user taps elsewhere.

/* BUG: hover state sticks on mobile */
.card:hover { background: #f0f0f0; }

/* FIX: only apply hover on devices that support it */
@media (hover: hover) and (pointer: fine) {
  .card:hover { background: #f0f0f0; }
}

Accessibility Mistakes

Mistake 16: Removing Focus Outlines

/* BAD: removes keyboard focus indicator */
*:focus {
  outline: none;
}

/* Users who navigate with keyboard can't see where they are */

Fix: Replace with a custom focus style.

/* Remove default only for mouse clicks, keep for keyboard */
:focus:not(:focus-visible) {
  outline: none;
}

/* Custom focus ring for keyboard navigation */
:focus-visible {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

Mistake 17: Low Color Contrast

Text must have sufficient contrast against its background. WCAG 2.1 requirements:

Normal text:  4.5:1 contrast ratio minimum
Large text:   3:1 contrast ratio minimum (18px+ bold, or 24px+)
/* BAD: light gray on white โ€” fails WCAG */
.text { color: #c0c0c0; background: #ffffff; }
/* Contrast ratio: ~1.6:1 โ€” FAILS */

/* GOOD: dark gray on white โ€” passes WCAG */
.text { color: #4b5563; background: #ffffff; }
/* Contrast ratio: ~6.3:1 โ€” PASSES */

Use browser DevTools or online contrast checkers to verify.

Mistake 18: Small Touch Targets

/* BUG: too small to tap accurately */
.small-link {
  font-size: 12px;
  padding: 2px;
  /* Tappable area: ~16x16px โ€” too small */
}

/* FIX: minimum 44x44px touch target */
.small-link {
  font-size: 12px;
  padding: 14px;  /* increases touch area */
  min-width: 44px;
  min-height: 44px;
}

/* Or expand hit area without visual change */
.small-link {
  position: relative;
}
.small-link::after {
  content: '';
  position: absolute;
  inset: -10px;  /* expands clickable area */
}

Mistake 19: Ignoring prefers-reduced-motion

Some users experience motion sickness from animations:

/* BAD: no motion preference respected */
.element { animation: bounce 1s infinite; }

/* GOOD: respect user's preference */
.element { animation: bounce 1s infinite; }

@media (prefers-reduced-motion: reduce) {
  .element { animation: none; }
}

/* BETTER: global reduction */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Mistake 20: Using Only Color to Convey Information

/* BAD: only color distinguishes error from success */
.error   { color: red; }
.success { color: green; }

/* Colorblind users can't distinguish these */

Fix: Add icons, borders, or text labels alongside color.

.error {
  color: #dc2626;
  border-left: 4px solid #dc2626;
}

.success {
  color: #16a34a;
  border-left: 4px solid #16a34a;
}
<div class="error">โš  Error: Please fix this field</div>
<div class="success">โœ“ Saved successfully</div>

Performance Mistakes

Mistake 21: Animating Expensive Properties

Layout properties (width, height, top, left, margin, padding) trigger the browser to recalculate the layout of potentially hundreds of elements every frame.

/* BAD: animates left (triggers layout every frame) */
.element {
  position: relative;
  left: 0;
  transition: left 0.3s;
}
.element:hover {
  left: 100px;
}

/* GOOD: animates transform (GPU-composited, no layout) */
.element {
  transition: transform 0.3s;
}
.element:hover {
  transform: translateX(100px);
}
Cheap to animate: transform, opacity
Moderate: color, background-color
Expensive: width, height, top, left, margin, padding
Very expensive: box-shadow (large), filter (complex)

Mistake 22: Applying will-change Everywhere

/* BAD: promotes everything to GPU layers, wastes memory */
* {
  will-change: transform;
}

/* BAD: permanent will-change on static elements */
.card {
  will-change: transform;  /* never actually animated */
}

Fix: Apply will-change only when needed, remove after.

/* GOOD: apply only during animation */
.card:hover {
  will-change: transform;
  transform: translateY(-4px);
}

Mistake 23: Huge Selector Lists

/* BAD: browser evaluates selectors right to left */
body > div > main > section > article > div > p > span a.link { }
/* Browser checks every <a> for .link, then walks up the tree */

/* GOOD: simple selector */
.article-link { }

Deeply nested selectors are slower (though modern browsers have optimized this significantly). More importantly, they're fragile โ€” any HTML structure change breaks them.

Mistake 24: Unused CSS

Large CSS files slow down initial render. The browser must parse the entire stylesheet before rendering.

Signs of unused CSS:
- CSS file > 50KB for a simple site
- Styles for deleted components
- Imported framework CSS where you use 10% of it

Fix: Audit with DevTools.

Chrome DevTools โ†’ Coverage tab โ†’ Reload โ†’ Shows unused CSS/JS percentage.

Tools to remove unused CSS:

  • PurgeCSS (build-time)
  • Tailwind CSS (auto-purges)
  • Chrome Coverage tab (manual audit)

Mistake 25: Excessive box-shadow or filter

/* EXPENSIVE: multiple complex shadows */
.card {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.1),
    0 4px 8px rgba(0,0,0,0.1),
    0 8px 16px rgba(0,0,0,0.1),
    0 16px 32px rgba(0,0,0,0.1);
}

/* CHEAPER: single shadow */
.card {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* CHEAPEST: use a pseudo-element with opacity animation */
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  opacity: 0;
  transition: opacity 0.3s;
}
.card:hover::after {
  opacity: 1;
}

General Mistakes

Mistake 26: Not Using CSS Reset

Browsers have different default styles. Without a reset, your site looks different in Chrome, Firefox, and Safari.

/* Minimal modern reset */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  -moz-text-size-adjust: none;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}

body {
  min-height: 100vh;
  line-height: 1.5;
}

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

Mistake 27: z-index Guessing

/* BAD: random z-index values */
.dropdown { z-index: 50; }
.modal { z-index: 999; }
.tooltip { z-index: 99999; }
.notification { z-index: 9999999; }
/* Escalation without end */

Fix: Use a z-index scale.

:root {
  --z-below: -1;
  --z-default: 0;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-overlay: 300;
  --z-modal: 400;
  --z-toast: 500;
  --z-tooltip: 600;
}

.dropdown { z-index: var(--z-dropdown); }
.modal    { z-index: var(--z-modal); }
.tooltip  { z-index: var(--z-tooltip); }

Mistake 28: Ignoring the Cascade

CSS stands for Cascading Style Sheets. Understanding source order and specificity prevents most conflicts.

/* Later rules override earlier rules (same specificity) */
.button { color: blue; }
.button { color: red; }  /* red wins */

/* File order matters too */
/* base.css loaded before theme.css */
/* theme.css rules win (same specificity) */

Use the cascade intentionally:

  1. Base/reset styles first (lowest specificity).
  2. Component styles next.
  3. Utility/override styles last (win via source order).

Mistake 29: Not Testing in Multiple Browsers

CSS rendering differs across browsers. Common differences:

  • Font rendering (antialiasing differs between OS/browser).
  • Scrollbar width (Windows shows scrollbars, Mac hides them).
  • Default form element styling (every browser differs).
  • Newer features (subgrid, container queries, :has()).

Minimum testing:

  • Chrome (Chromium)
  • Firefox (Gecko)
  • Safari (WebKit)
  • Mobile Safari (iOS)
  • Chrome on Android

CSS Debugging Checklist

When something looks wrong, check these in order:

1. Is the element visible?
   - Check display, visibility, opacity, overflow, z-index
   - Check if it's behind another element

2. Is the size correct?
   - Check box-sizing (border-box vs content-box)
   - Check width, height, min/max dimensions
   - Check padding and border adding to size

3. Is the position correct?
   - Check position value (static, relative, absolute, fixed, sticky)
   - Check for positioned ancestor (for absolute elements)
   - Check for transform/filter on ancestors (breaks fixed positioning)

4. Is the spacing correct?
   - Check margin collapse (vertical margins between siblings/parent-child)
   - Check padding vs margin
   - Check gap (in flex/grid containers)

5. Is the style being applied?
   - Check specificity (DevTools Styles panel โ€” look for strikethroughs)
   - Check source order
   - Check for !important overrides
   - Check for typos in property names or values

6. Is it a browser-specific issue?
   - Test in Chrome, Firefox, Safari
   - Check caniuse.com for feature support
   - Look for vendor prefixes needed

Quick Reference: Fix Table

ProblemCommon CauseFix
Element wider than expectedcontent-box sizingbox-sizing: border-box
Margin appears outside parentMargin collapsedisplay: flow-root on parent
Percentage height doesn't workParent has no heightSet explicit height on parent chain
Flex item overflowsmin-width: auto defaultmin-width: 0
z-index not workingElement is position: staticAdd position: relative
Sticky not stickingParent has overflowRemove overflow from ancestor
Fixed element not fixedAncestor has transformRemove transform or restructure
iOS zoom on input focusFont-size < 16pxfont-size: 16px minimum
Horizontal scrollbar100vw includes scrollbarUse 100% instead
Mobile 100vh too tallIncludes browser chromeUse 100dvh
Hover sticks on mobileTouch devices lack hover@media (hover: hover) guard

Key Takeaways

  • Always use box-sizing: border-box globally. It prevents the most common sizing bugs.
  • Margin collapse is not a bug โ€” it's by design. Understand it, use display: flow-root to prevent it when needed.
  • Keep specificity flat. Use single-class selectors. Avoid IDs for styling. Avoid !important.
  • Use 100dvh instead of 100vh on mobile for full-height sections.
  • Never remove focus outlines without providing an alternative. Use :focus-visible.
  • Animate only transform and opacity for smooth 60fps animations. Avoid animating width, height, margin, or top/left.
  • Test on real devices and multiple browsers. DevTools simulation misses real-world issues.
  • Respect user preferences: prefers-reduced-motion, prefers-color-scheme, browser font size settings.
  • Use a CSS reset to normalize cross-browser differences.
  • When debugging: check specificity first (DevTools Styles panel), then box model, then positioning.

Found this helpful?

Support devsofus โ€” help us keep creating free dev guides.

Related Articles