The four numbers (plus a keyword)
A box-shadow is offset-x offset-y blur spread color, with an optional inset:
| Value | Controls | Effect |
|---|---|---|
| offset-x / y | Direction | Where the shadow falls |
| blur | Softness | 0 = hard edge, higher = diffuse |
| spread | Size | Grows (+) or shrinks (−) the shadow |
| color | Tint | Usually low-opacity black |
inset | Inside vs outside | Recessed/inner shadow |
The most underused of these is spread: a positive spread with zero blur and zero offset creates a solid outline that doesn’t affect layout, and a negative spread tightens a shadow so only part of it shows — handy for subtle bottom-only elevation.
Color the shadow, don’t just darken it
Flat-looking shadows are often pure black. Real shadows pick up the surface and ambient color, so a subtly tinted shadow (a dark version of the element’s hue, or a navy-tinted shadow on a blue UI) reads richer than rgba(0,0,0,0.5). Keep opacity low — 10–25% is the sweet spot for believable elevation; heavier shadows look heavy. The element can even cast a shadow in its own color for a glow effect by matching the shadow color to the element.
Two performance traps
- Overflow clipping — box-shadow is drawn outside the box, so a parent with
overflow: hidden(common on cards, carousels, modals) clips it. Either pad the container or move the shadow to a wrapper — or switch todrop-shadow(), which overflow doesn’t clip. - Animation jank — animating
box-shadowdirectly forces a repaint every frame and stutters with large blur. Instead, put the “hover” shadow on a pseudo-element and animate itsopacity(GPU-composited), or addwill-changeas a hint. The shadow appears to grow smoothly without the repaint cost.
Use the live preview here to tune offset, blur, and opacity, then layer a few of the generated shadows together for the realistic look described above.