box-shadow draws the element's border box; filter: drop-shadow() follows the alpha shape of an image. Same-looking values are not interchangeable — the blur number even means something different.
CSS gives you three shadow primitives, and the choice between them is decided by one question: what shape should the shadow have? box-shadow shadows the element's box, text-shadow shadows each glyph, and filter: drop-shadow() shadows whatever pixels the element actually paints. The last two look like competitors, but they answer different questions — and copying a value from one to the other quietly changes how it renders.
Both demos below use the same transparent star image. The first applies box-shadow to the image element; the second applies filter: drop-shadow() to it. Watch where the shadow comes from.
Same element, different silhouette
box-shadow: 0 8px 16px rgba(16, 24, 40, 0.25);filter: drop-shadow(0 4px 8px rgba(16, 24, 40, 0.25));box-shadow is drawn from the border box, so it ignores transparency inside the element and fills the rectangle behind it — rounded if border-radius says so. drop-shadow() blurs and offsets the element's alpha mask, so the shadow only appears where the star's pixels do. On a fully opaque card both look nearly identical; on anything transparent they are completely different effects.
What each one accepts
| box-shadow | text-shadow | filter: drop-shadow() | |
|---|---|---|---|
| Silhouette | Border box (border-radius applies) | Every glyph's outline | Element's alpha shape (pixels) |
| Offsets | X and Y | X and Y | X and Y |
| Blur | Blur radius (2× the Gaussian σ) | Blur radius (2× the Gaussian σ) | Gaussian standard deviation (σ) |
| Spread | Yes — 4th length | No | No |
| inset | Yes | No | No |
| Multiple layers | Comma-separated, first paints on top | Comma-separated, first paints on top | Chain functions: drop-shadow() drop-shadow() |
| Where it applies | Any box-level element | Text content | Any element, via the filter property |
box-shadow, text-shadow, and drop-shadow() side by side.
The blur number does not transfer
This is the trap. box-shadow defines its blur radius so that the painted transition spans roughly that many pixels — per spec it is twice the standard deviation of the underlying Gaussian. drop-shadow() takes that standard deviation directly as its third length. The Filter Effects spec flags it: values are interpreted as for box-shadow, but the third value is the standard deviation, not the blur radius.
So filter: drop-shadow(0 4px 8px black) and box-shadow: 0 4px 8px black do not match — the filter version is twice as blurry. To port a box-shadow, halve the blur; to port a drop-shadow, double it. Offsets are literal in both, so only the third number moves.
Equivalent pair — same visual blur
box-shadow: 0 4px 16px rgba(16, 24, 40, 0.25);
filter: drop-shadow(0 4px 8px rgba(16, 24, 40, 0.25));Layers behave differently too
box-shadow layers are peers in one list: each paints independently, first listed on top, so a crisp ring can sit above a soft drop in a single declaration. drop-shadow() has no comma list — the spec for this level accepts no spread and no multiple shadows. You can chain functions (filter: drop-shadow(...) drop-shadow(...)), but chained filters apply in sequence to the output of the previous one, so the second shadow falls on the first shadow's result rather than painting as a separate sheet.
inset is equally unavailable: a filter cannot draw inside the border box. Pressed wells, recessed inputs, and the dark half of soft-UI surfaces are box-shadow-only territory.
A practical rule
- Cards, buttons, panels, inputs — anything rectangular or border-radius'd — box-shadow. It gives you spread for rings, inset for pressed states, and comma layers for contact-plus-ambient depth.
- PNG cut-outs, SVG icons, masked shapes, emoji — drop-shadow(), because the silhouette should follow the artwork, not its bounding rectangle.
- Headlines and labels — text-shadow, which traces the glyphs and works without filters.
- Focus rings — box-shadow with zero blur and a spread (0 0 0 2px), one of the most-shipped shadows in our measurement of top-1,000 sites.
What about cost?
A filter operates on rendered pixels: when the element or its shadow changes, the browser re-renders the filtered layer. box-shadow participates in normal element painting. For static UI the difference is not something you will feel, but it is one reason drop-shadow() is not a drop-in upgrade for every card — reach for it when the shape demands it, not as a default. Honesty first: measure if you have a real problem, and skip the folklore benchmarks otherwise.