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
| Quirk | Problem | Solution |
|---|---|---|
| Grid-gap overflow | % + gap > 100% | Use fr units |
| z-index ignored | No positioning | Add position |
| Margin collapse | Adjacent margins merge | Use flex/grid gap |
| % height fails | No parent height | Set parent height |
| Flex shrink | Items shrink below width | min-width or flex-shrink: 0 |
| 100vh mobile | Browser chrome | Use 100dvh |
Conclusion
CSS quirks are predictable once you understand the rules:
- Grid-gap overflow — Use
frunits, not percentages - Hidden dependencies — z-index needs position, width needs block display
- Margin collapse — Use flexbox/grid gaps instead
- Viewport units — Use
dvhfor 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