Type something to search...
Common CSS Mistakes Beginners Make and How to Fix Them

Common CSS Mistakes Beginners Make and How to Fix Them

Everyone who learns CSS goes through the same rite of passage. You write what looks like perfectly reasonable code, refresh the page, and something is inexplicably wrong. A box is wider than you asked for. A z-index of 9999 does nothing. Text looks fine on your laptop and overflows on your phone. You add !important, then another, and eventually the stylesheet feels like a house of cards.

The good news is that most of these problems come from a small, predictable set of mistakes. Once you recognize them, you'll spot them in seconds, and you'll write CSS that behaves the way you expect from the start.

In this guide, I'll walk through the most common CSS mistakes beginners make, explain why each one happens, and show the fix with code you can copy.

1. Forgetting About box-sizing

The symptom: You set a box to width: 300px, add some padding and a border, and it ends up 342px wide. Or you set a column to width: 50% with padding and the layout wraps onto a new line.

Why it happens: By default, CSS uses box-sizing: content-box. The width you set only applies to the content area. Padding and border are added on top.

.card {
  width: 300px;
  padding: 20px;
  border: 1px solid #cbd5e1;
  /* Actual rendered width: 300 + 40 + 2 = 342px */
}

The fix: Switch to border-box globally, so width includes padding and border. This is in virtually every modern CSS reset:

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

Now width: 300px means 300 pixels on screen, full stop.

2. Using z-index Without Understanding Stacking

The symptom: A dropdown menu hides behind the content below it. You give it z-index: 9999 and nothing changes.

Why it happens: Two reasons usually. First, on a normal block element with position: static, z-index has no effect. It works on positioned elements (relative, absolute, fixed, sticky) and on flex and grid items. Second, z-index values only compete within the same stacking context. If the dropdown's parent creates its own stacking context, for example with transform, opacity below 1, filter, or its own z-index, the dropdown can never escape above elements outside that parent, no matter how big the number.

<header class="site-header">
  <nav class="menu">
    <ul class="dropdown">
      ...
    </ul>
  </nav>
</header>
<main class="content">...</main>
/* Problem: the transform creates a stacking context */
.site-header {
  transform: translateZ(0);
}

.dropdown {
  position: absolute;
  z-index: 9999; /* trapped inside .site-header */
}

The fix: Give the element a position if it doesn't have one, and raise the parent that forms the stacking context, not just the child:

.site-header {
  position: relative;
  z-index: 10;
}

.dropdown {
  position: absolute;
  z-index: 1;
}

Better still, avoid unnecessary properties that create stacking contexts, and keep a small, named z-index scale:

:root {
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-modal: 300;
  --z-toast: 400;
}

For true overlays like modals, the native dialog element with showModal() and the popover attribute render in the browser's top layer, above everything, with no z-index needed at all.

3. Setting Fixed Heights on Content

The symptom: A card looks perfect with your placeholder text. Then real content arrives, the text is longer, and it spills out the bottom of the box, overlapping whatever comes next.

Why it happens: height: 200px means exactly 200px, whatever is inside.

The fix: Let content determine height. If you need a minimum, use min-height:

/* Fragile */
.card {
  height: 200px;
}

/* Resilient */
.card {
  min-height: 200px;
}

For media, use aspect-ratio instead of a fixed height, so the shape stays consistent at any width:

.card-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

4. Using 100vh for Full-Screen Sections on Mobile

The symptom: A full-screen hero looks right on desktop, but on phones the bottom is cut off behind the browser's toolbar, and the page jumps when you scroll.

Why it happens: On mobile, 100vh is based on the largest possible viewport, as if the address bar were hidden. When the address bar is visible, the section is taller than the visible area.

The fix: Use the newer dynamic viewport units. dvh tracks the actual visible height, while svh uses the smallest viewport height (stable, no jumping):

.hero {
  min-height: 100vh; /* fallback for older browsers */
  min-height: 100svh;
}

Those units are supported in all current major browsers. Listing vh first gives older browsers a sensible fallback.

5. Sizing Text in Pixels Only

The symptom: Users who increase their browser's default font size for readability see no change on your site.

Why it happens: Font sizes set in px ignore the user's default font-size preference. (Browser zoom still works, but the font-size setting doesn't.)

The fix: Use rem for font sizes. 1rem equals the root font size, which respects the user's setting:

html {
  /* Don't set a px font size here; leave the user's default */
}

body {
  font-size: 1rem;
  line-height: 1.6;
}

h1 {
  font-size: 2.5rem;
}

.small-print {
  font-size: 0.875rem;
}

For headings that should scale with the screen, combine rem with clamp():

h1 {
  font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
}

6. Reaching for !important to Win Specificity Battles

The symptom: A style won't apply, so you add !important. Later, something else needs to override that, so it gets !important too. Soon, half your declarations have it.

Why it happens: Another rule has higher specificity. For example, an ID selector or a longer chain beats your simple class.

#sidebar .widget a {
  color: #334155;
}

/* This loses, because the selector above has an ID */
.link-accent {
  color: #db2777;
}

The fix: Lower specificity instead of escalating. Avoid IDs for styling, keep selectors short, and use cascade layers or :where() for defaults:

.widget-link {
  color: #334155;
}

.link-accent {
  color: #db2777;
}

Now both have the same specificity, and the one that comes later wins. Use DevTools to see which rule is winning: overridden declarations are crossed out in the Styles pane, and hovering over a selector shows its specificity.

!important still has legitimate uses, such as utility classes that must always apply, but it shouldn't be your go-to fix.

7. Removing Focus Outlines

The symptom: There isn't one for you. There is for keyboard users, who can no longer see where they are on the page.

Why it happens: Browser focus rings can look out of place in a design, so people write this:

/* Please don't */
*:focus {
  outline: none;
}

The fix: Style focus instead of removing it. :focus-visible shows the indicator when the browser decides it's useful, typically for keyboard navigation, without showing it on every mouse click:

:focus-visible {
  outline: 3px solid #38bdf8;
  outline-offset: 2px;
}

This keeps your site usable for keyboard users, and it's much nicer looking than the default.

8. Expecting width and Vertical Margins to Work on Inline Elements

The symptom: You set width, height, or margin-top on a span or a, and nothing happens.

Why it happens: Inline elements flow like text. They ignore width and height, and their vertical margins don't push surrounding lines away.

The fix: Change the display type:

.tag {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  margin-block: 0.25rem;
  border-radius: 999px;
  background: #e0e7ff;
}

.nav-link {
  display: block;
  padding: 0.75rem 1rem;
}

inline-block keeps the element in the line of text while respecting box dimensions. block, flex, or grid take the element out of the text flow.

9. Confusing Margin Collapse for a Bug

The symptom: Two stacked paragraphs have margin-bottom: 24px and margin-top: 24px, but the gap between them is 24px, not 48px. Or a child's top margin pokes out of its parent, pushing the whole parent down.

Why it happens: Adjacent vertical margins in normal block flow collapse into the larger of the two. It's intended behavior, not a bug.

The fix: Use one-directional margins, so you only ever set margin on one side:

.prose > * + * {
  margin-top: 1rem;
}

Or use flex or grid with gap, where margins don't collapse:

.stack {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

If a child's margin escapes its parent, adding display: flow-root to the parent creates a new formatting context and contains it.

10. Flex and Grid Items Overflowing Their Container

The symptom: A long URL or a wide code block inside a flex or grid item pushes the whole layout wider than the screen, causing horizontal scrolling.

Why it happens: Flex items default to min-width: auto, which means they won't shrink smaller than their content. Grid tracks sized with 1fr have a similar content-based minimum.

The fix: Allow the item to shrink below its content size:

.layout {
  display: flex;
  gap: 2rem;
}

.layout-main {
  flex: 1;
  min-width: 0;
}

For grid, use minmax(0, 1fr) instead of 1fr:

.layout {
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
}

Then handle the long content itself:

.layout-main pre {
  overflow-x: auto;
}

.layout-main a {
  overflow-wrap: anywhere;
}

11. Percentage Heights That Do Nothing

The symptom: You set height: 100% on an element, and it collapses to the height of its content.

Why it happens: A percentage height is a percentage of the parent's height. If the parent's height is determined by its content (auto), there's nothing to calculate from, so the percentage is ignored.

The fix: In most cases you don't need percentage heights. Use flexbox or grid to stretch children:

body {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  margin: 0;
}

That's the classic header, main, footer layout with the footer pinned to the bottom when content is short, and no percentage heights anywhere.

12. Images Breaking Out of Layouts

The symptom: A large image overflows its column on smaller screens. Or there's a mysterious few pixels of space under every image.

Why it happens: Images display at their natural size unless told otherwise. And because they're inline by default, they sit on the text baseline, leaving room below for letter descenders.

The fix: A pair of rules that belong in every reset:

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

Also include width and height attributes in the HTML, so the browser can reserve space and avoid layout shift while the image loads:

<img
  src="/images/team.jpg"
  alt="Our team at the 2026 offsite"
  width="1200"
  height="800"
/>

13. Writing Desktop-First and Patching Mobile

The symptom: Your stylesheet is full of max-width media queries undoing desktop styles for smaller screens, and every new component needs more overrides.

Why it happens: Starting from the most complex layout means every smaller screen has to take things away.

The fix: Write the simple, single-column layout first, then add complexity as space allows:

.features {
  display: grid;
  gap: 1.5rem;
}

@media (width >= 48rem) {
  .features {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (width >= 64rem) {
  .features {
    grid-template-columns: repeat(3, 1fr);
  }
}

The range syntax (width >= 48rem) is supported in current browsers and reads more naturally than min-width. Better yet, for many grids you can skip media queries entirely:

.features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: 1.5rem;
}

14. Syntax Slips That Silently Break Rules

The symptom: A whole block of styles just doesn't apply, and there's no error message anywhere.

Why it happens: CSS fails quietly. A missing semicolon makes the browser read two declarations as one invalid value, and both get dropped. A missing closing brace can swallow the rules that follow.

.alert {
  color: #b91c1c
  background: #fef2f2; /* dropped along with the line above */
}

The fix: Use an editor with CSS linting, run Stylelint in your project, and format with Prettier, which makes structural mistakes obvious. In DevTools, invalid declarations appear with a warning icon and strike-through.

15. Magic Numbers

The symptom: Code like top: 37px, margin-left: -3px, or width: 283px that nobody can explain, and that breaks as soon as a font or padding changes.

Why it happens: A value was tweaked until it looked right in one situation.

The fix: Use layout tools that align things by design, such as flexbox alignment, grid, gap, and place-items: center, instead of nudging by pixels. When you do need a specific value, derive it from something meaningful, like a custom property or calc():

:root {
  --header-height: 4rem;
}

.site-header {
  height: var(--header-height);
}

.page-content {
  scroll-margin-top: var(--header-height);
}

Conclusion

Most CSS frustration comes from the same handful of mistakes: the default box model, misunderstood stacking contexts, fixed heights, pixel-only text, specificity battles, removed focus styles, collapsing margins, and flex items that refuse to shrink. None of them mean you're bad at CSS. They mean CSS has rules that aren't obvious until someone points them out.

Start every project with a small reset (border-box, responsive images), use relative units and modern layout tools, keep selectors simple, and lean on DevTools to see what's really happening. Fix these fifteen habits, and you'll spend a lot less time fighting your stylesheet and a lot more time building with it.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading