CSS has two completely different shadow systems and they do not behave the same way. box-shadow follows the rectangular box of the element. filter: drop-shadow() follows the actual shape of the visible pixels. Choosing the wrong one gives you unexpected results.
box-shadow renders a shadow based on the element's border-box — always a rectangle (or rounded rectangle if border-radius is set). It does not care what is inside the element.
box-shadow: 4px 4px 12px rgba(0,0,0,0.3);
filter: drop-shadow() renders a shadow based on the alpha channel of the rendered element — the visible pixels. Transparent areas do not cast shadows.
filter: drop-shadow(4px 4px 12px rgba(0,0,0,0.3));
For a solid rectangular div, both look nearly identical. The difference becomes visible with:
clip-path to create non-rectangular shapesborder-radius: 50% and transparency| box-shadow | filter: drop-shadow() | |
|---|---|---|
| Full syntax | h-offset v-offset blur spread color inset | drop-shadow(h-offset v-offset blur color) |
| Example | box-shadow: 4px 6px 12px 0 rgba(0,0,0,0.3) | filter: drop-shadow(4px 6px 12px rgba(0,0,0,0.3)) |
| Spread value | ✓ Supported | ✗ Not supported |
| Inset | ✓ Supported | ✗ Not supported |
| Multiple shadows | ✓ Comma-separated | ~Chain filter functions |
| What gets shadow | The border-box rectangle | The visible pixels (alpha channel) |
Use box-shadow for anything rectangular — which is most UI elements:
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);box-shadow: 0 2px 8px rgba(0,0,0,0.15);box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);box-shadow: 0 20px 60px rgba(0,0,0,0.3);box-shadow: 0 4px 6px -2px rgba(0,0,0,0.15);box-shadow: 0 0 0 3px rgba(59,130,246,0.5);Box-shadow supports spread (expand/contract), inset (inner shadows), and efficient multiple layers. It covers 90% of shadow use cases in web development.
Use filter: drop-shadow() when the shape matters:
Example with a transparent PNG:
img.product-photo {
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
}
This creates a shadow that follows the product outline. box-shadow would create a rectangular shadow around the entire image box, including the transparent areas — visually wrong.
| Metric | box-shadow | filter: drop-shadow() |
|---|---|---|
| Rendering pipeline | Paint step (layer paint) | Filter compositing (offscreen buffer) |
| GPU acceleration | Partial (static is CPU, animate via pseudo-element trick) | Partial (filter compositing uses GPU on some browsers) |
| Animation cost | ~Moderate — triggers repaint per frame | ~Higher — triggers filter recalculation per frame |
| Multiple shadows | ✓ Single paint pass for all | ~Each filter function = separate compositing pass |
| Impact on static elements | ✓ Negligible | ✓ Negligible |
| Impact on animated elements | ~Avoid animating directly; use opacity trick | ~Avoid animating; more expensive than box-shadow |
For static elements, both are fine. For animations (hover transitions, scroll effects), box-shadow is the lighter option. For either, the most performant approach is to put the shadow on a ::before or ::after pseudo-element and animate its opacity instead of the shadow property itself.
If you use clip-path on an element with box-shadow, the shadow gets clipped too — because box-shadow is part of the element's rendering. The shadow disappears.
Solution: apply filter: drop-shadow() to a parent wrapper around the clipped element. The parent is not clipped, so the shadow renders based on the child's visible shape:
.shadow-wrapper {
filter: drop-shadow(4px 4px 8px rgba(0,0,0,0.3));
}
.clipped-shape {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
background: #3b82f6;
}
This renders a triangle with a triangle-shaped shadow — something box-shadow cannot do.
| Your Element | Use This | Why |
|---|---|---|
| Cards, buttons, containers | box-shadow | Rectangular. Supports spread, inset, layers. |
| Input fields (pressed look) | box-shadow (inset) | Only box-shadow supports inset. |
| Transparent PNG image | filter: drop-shadow() | Shadow follows image outline, not rectangle. |
| SVG icon | filter: drop-shadow() | Shadow traces icon shape. |
| clip-path shape | filter: drop-shadow() on parent | box-shadow gets clipped with the shape. |
| Focus ring / outline | box-shadow (spread) | Zero-blur spread acts as border without layout shift. |
| Animated hover shadow | box-shadow (with pseudo-element) | Lower repaint cost than filter. |
Build box-shadow CSS visually — adjust offset, blur, spread, color, inset.
Open Box Shadow Generator