Anatomy of box-shadow
The CSS syntax is box-shadow: x y blur spread color;:
- X: horizontal offset. Positive = right. For realistic shadows almost always 0.
- Y: vertical offset. Positive = down. Usually 4-20px to emulate top-down lighting.
- Blur: shadow softness. Low values (0-4px) give brutalist hard shadows; high (20-50px) give modern soft ones.
- Spread: how much the shadow expands before blur. Negative shrinks, positive enlarges. Almost always 0.
- Color: ideally rgba with low opacity (4-15%). Pure black at 100% looks plastic.
The multi-shadow trick
The "premium" shadows you see on Stripe, Vercel or Linear aren't a single shadow: they're two or three stacked. One short and crisp close to the element (1-2px Y, low blur, medium opacity), and a long diffuse one further down (8-20px Y, high blur, low opacity). It mimics how real light casts both a direct and an indirect shadow.
box-shadow:
0 1px 2px rgba(9, 9, 11, 0.06),
0 4px 12px rgba(9, 9, 11, 0.08); Inset: when to use it
An inset shadow goes inside the element and simulates reversed depth.
Legit uses: pressed-state inputs, containers that look sunken into the page,
active drop zones. Illegit use: every button (2010s skeuomorphism — it
does not age well).
Shadows in dark mode
Shadows are far less visible on dark backgrounds: black on black is nearly invisible. Solutions:
- Light borders instead of shadows:
border: 1px solid rgba(255,255,255,0.08). - Higher opacity shadows (20-40% instead of 8-12%) with bigger blur.
- Colored shadows: in dark mode, a low-opacity shadow tinted with your brand color produces a "glow" rather than depth.
Performance: when it can tank your frame rate
Box-shadow is computed on the CPU (not GPU). Animating blur on 100 elements at
once tanks the frame rate. If you need to animate shadows (e.g. on hover), use
transform instead of changing blur, or use
filter: drop-shadow() which is GPU-accelerated and respects
transparent edges (useful for SVG and images with backgrounds).
Four shadows that work in any UI
- Card subtle:
0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04) - Card hover:
0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.05) - Modal / popover:
0 12px 32px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06) - Floating button:
0 6px 20px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.08)