Skip to main content
All articles

Developer

box-shadow vs filter: drop-shadow() — Which CSS Shadow to Use

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.

6 min readSep 24, 2026GainUtility

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 on a transparent image — a rectangle behind the whole box
box-shadow: 0 8px 16px rgba(16, 24, 40, 0.25);
filter: drop-shadow() on the same image — the shadow follows the star
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-shadowtext-shadowfilter: drop-shadow()
SilhouetteBorder box (border-radius applies)Every glyph's outlineElement's alpha shape (pixels)
OffsetsX and YX and YX and Y
BlurBlur radius (2× the Gaussian σ)Blur radius (2× the Gaussian σ)Gaussian standard deviation (σ)
SpreadYes — 4th lengthNoNo
insetYesNoNo
Multiple layersComma-separated, first paints on topComma-separated, first paints on topChain functions: drop-shadow() drop-shadow()
Where it appliesAny box-level elementText contentAny 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.