Asher Cohen
Back to posts

Weird CSS: Surprising Behaviors and Hidden Dependencies

Exploring CSS quirks — grid-gap overflow, hidden dependencies, stacking contexts, and other unexpected behaviors

Introduction

CSS can behave in surprising ways that frustrate even experienced developers. Understanding these quirks — from grid-gap overflow to hidden dependencies — helps you debug layout issues faster and write more predictable styles.

Grid-Gap Causes Overflow

The most common CSS Grid surprise: percentage columns plus gaps exceed 100%:

/* ❌ Overflows — 25% + 50% + 25% + 10px + 10px > 100% */
.grid {
  display: grid;
  grid-template-columns: 25% 50% 25%;
  gap: 10px;
}

/* ✅ Solution 1: fr units (recommended) */
.grid {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 10px;
}

/* ✅ Solution 2: minmax() */
.grid {
  display: grid;
  grid-template-columns: minmax(auto, 25%) minmax(auto, 50%) minmax(auto, 25%);
  gap: 10px;
}

/* ✅ Solution 3: calc() */
.grid {
  display: grid;
  grid-template-columns: repeat(3, calc(33.333% - 6.667px));
  gap: 10px;
}

Hidden CSS Dependencies

CSS properties often depend on other properties being set:

z-index Requires Positioning

/* ❌ z-index has no effect without positioning */
.modal {
  z-index: 1000; /* Ignored! */
}

/* ✅ z-index works with positioned elements */
.modal {
  position: relative; /* or absolute, fixed, sticky */
  z-index: 1000;
}

width/height on Inline Elements

/* ❌ width/height ignored on inline elements */
span {
  width: 200px;  /* Ignored! */
  height: 100px; /* Ignored! */
}

/* ✅ Use inline-block or block */
span {
  display: inline-block;
  width: 200px;
  height: 100px;
}

top/left Require Positioning

/* ❌ top/left have no effect in static position */
.element {
  top: 20px; /* Ignored! */
}

/* ✅ Works with positioned elements */
.element {
  position: relative;
  top: 20px;
}

transform Creates New Stacking Context

/* ⚠️ transform: scale(1) creates a new stacking context */
.card {
  transform: scale(1); /* Changes z-index behavior! */
}

/* This can cause unexpected z-index issues */

Margin Collapse

Adjacent vertical margins collapse into a single margin:

/* ❌ Surprising — gap is 20px, not 40px */
.top {
  margin-bottom: 20px;
}
.bottom {
  margin-top: 20px;
}
/* Space between them: 20px (not 40px!) */

/* ✅ Solutions */
/* 1. Use padding instead */
/* 2. Add border/padding to parent */
/* 3. Use flexbox/grid gap */
.parent {
  display: flex;
  flex-direction: column;
  gap: 20px; /* No collapse! */
}

Percentage Heights

Percentage height only works if the parent has an explicit height:

/* ❌ height: 100% doesn't work without parent height */
.child {
  height: 100%; /* May be ignored! */
}

/* ✅ Parent needs explicit height */
.parent {
  height: 100vh; /* or any explicit value */
}
.child {
  height: 100%; /* Now works */
}

Flexbox Shrink Surprise

Flex items shrink by default, even with explicit widths:

/* ❌ Items may shrink below 200px */
.flex-item {
  flex: 0 1 200px; /* flex-shrink: 1 allows shrinking */
}

/* ✅ Prevent shrinking */
.flex-item {
  flex: 0 0 200px; /* flex-shrink: 0 */
  /* or */
  min-width: 200px;
}

Overflow and Border-Radius

Overflow hidden doesn't always clip border-radius children:

/* ⚠️ Child may overflow rounded corners */
.parent {
  border-radius: 10px;
  overflow: hidden; /* Usually clips, but not always */
}

/* ✅ Force clipping */
.parent {
  border-radius: 10px;
  overflow: hidden;
  transform: translateZ(0); /* Creates stacking context */
}

The 100vh Mobile Problem

100vh doesn't account for mobile browser chrome:

/* ❌ 100vh is taller than visible area on mobile */
.hero {
  height: 100vh; /* Scrolls on mobile! */
}

/* ✅ Use dvh (dynamic viewport height) */
.hero {
  height: 100dvh; /* Accounts for browser chrome */
}

/* ✅ Fallback */
.hero {
  height: 100vh;
  height: 100dvh;
}

Best Practices

Do's

/* ✅ Use fr units for grid columns with gaps */
grid-template-columns: 1fr 2fr 1fr;

/* ✅ Set position for z-index to work */
position: relative; z-index: 10;

/* ✅ Use flexbox/grid gap instead of margins */
display: flex; gap: 20px;

/* ✅ Use dvh for full-screen mobile sections */
height: 100dvh;

/* ✅ Set min-width to prevent flex shrinking */
min-width: 200px;

Don'ts

/* ❌ Don't mix percentage columns with fixed gaps */
grid-template-columns: 25% 50% 25%; gap: 10px;

/* ❌ Don't use z-index without positioning */
z-index: 100; /* No effect without position */

/* ❌ Don't rely on percentage heights without parent height */
height: 100%; /* May not work */

/* ❌ Don't use 100vh for mobile full-screen */
height: 100vh; /* Overflows on mobile */

/* ❌ Don't forget flex items shrink by default */
flex: 1 1 auto; /* May shrink unexpectedly */

Summary Table

QuirkProblemSolution
Grid-gap overflow% + gap > 100%Use fr units
z-index ignoredNo positioningAdd position
Margin collapseAdjacent margins mergeUse flex/grid gap
% height failsNo parent heightSet parent height
Flex shrinkItems shrink below widthmin-width or flex-shrink: 0
100vh mobileBrowser chromeUse 100dvh

Conclusion

CSS quirks are predictable once you understand the rules:

  • Grid-gap overflow — Use fr units, not percentages
  • Hidden dependencies — z-index needs position, width needs block display
  • Margin collapse — Use flexbox/grid gaps instead
  • Viewport units — Use dvh for mobile

These aren't bugs — they're features of the CSS specification. Learn them and your layouts will be more predictable.

#css #quirks #layout #grid #flexbox #web-development #frontend