CSSAWWWARDS
+ Submit tool
generatorsBeginner-Intermediate · 2026

CSS Shadows Explained: box-shadow vs text-shadow vs filter: drop-shadow() (2026)

CSS actually gives you three separate shadow mechanisms, not one — and picking the wrong one is why your icon's shadow looks like a grey rectangle instead of hugging its shape. Here's exactly when to reach for each.

By Adil Badshah11 August 202610 min read
CSS Shadows Explained: box-shadow vs text-shadow vs filter: drop-shadow() (2026)

CSS's Three Shadow Mechanisms

Quick Answer

CSS has three distinct shadow tools: box-shadow for the rectangular (or rounded) box of an element, text-shadow for text glyphs specifically, and filter: drop-shadow() for a shadow that follows the actual visible shape of any content — including transparent PNGs, SVGs, and clipped or masked elements.

It's easy to assume “shadow” is one CSS feature, because visually the results can look similar. But each of the three works on a fundamentally different input: box-shadow reads the element's box geometry, text-shadow reads its rendered glyph outlines, and drop-shadow() reads the actual rasterized alpha channel of everything inside the element. That difference in input is exactly why a box-shadow on a transparent PNG icon draws a shadow around the icon's invisible bounding rectangle instead of its visible silhouette — and why swapping in drop-shadow() fixes it instantly.

The one-line rule: if what needs a shadow is a box (a card, a button, a panel), use box-shadow. If it's text, use text-shadow. If it's an irregular shape — an icon, an SVG, a clipped element, or anything with real transparency — use filter: drop-shadow().

box-shadow: Shadows on Boxes

Quick Answer

box-shadow takes offset-x, offset-y, blur-radius, an optional spread-radius, and a color. Add inset to draw the shadow inside the box instead of outside. It accepts a comma-separated list, so you can layer multiple shadows on one element for realistic depth.

box-shadow is the workhorse for card elevation, button depth, focus rings, and modal overlays — anything where the shadow should trace the element's own box, including its border-radius. It respects rounded corners automatically, which is one reason it's usually the right first choice for UI chrome.

/* offset-x | offset-y | blur-radius | spread-radius | color */.card {  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);} /* inset flips it to the inside of the box */.pressed-button {  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);}

Layering multiple shadows for realistic depth

A single soft shadow reads as flat and slightly artificial. Real elevation — the kind you see in well-designed interfaces — is almost always two or three shadows stacked: a small, sharp, dark shadow close to the element for contact, and one or two larger, softer, lighter shadows further out for ambient depth.

/* Multiple shadows, comma-separated — first one paints on top */.elevated-card {  box-shadow:    0 1px 2px rgba(0, 0, 0, 0.08),   /* tight contact shadow */    0 8px 24px rgba(0, 0, 0, 0.12),  /* soft ambient shadow */    0 24px 48px rgba(0, 0, 0, 0.08); /* far, very soft depth */}

Shadows are painted in source order, with the first one in the list on top. This matters when shadows overlap — put your tightest, most opaque shadow first so it doesn't get visually buried under a larger, softer one. Rather than guessing offsets by hand, the Box Shadow Builder lets you layer, preview, and copy exactly this kind of multi-shadow elevation visually.

text-shadow: Shadows on Type

Quick Answer

text-shadow uses the same offset-x offset-y blur-radius color syntax as box-shadow (minus spread-radius and inset), but paints behind the actual glyph shapes of the text rather than a box. Like box-shadow, it accepts multiple comma-separated shadows.

box-shadow cannot be applied to text directly in any useful way — it only knows about an element's box, so a box-shadow on a heading draws a rectangle behind the whole line, not an outline around each letter. text-shadow exists specifically to solve this, tracing the rendered glyph outlines instead.

/* offset-x | offset-y | blur-radius | color */.headline {  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);} /* Neon glow — stack several with zero offset, growing blur */.neon {  color: #fff;  text-shadow:    0 0 4px #0ff,    0 0 12px #0ff,    0 0 24px #0ff,    0 0 48px #08f;}

The neon-glow technique above — stacking several shadows at zero offset with increasing blur radius — is the standard way to fake a glow effect in CSS, since there's no dedicated “glow” property. Each layer contributes a wider, softer halo, and stacking three or four produces a convincing gradient falloff that a single shadow can't — the CSS Text Shadow Generator has this and seven other type-effect presets ready to tweak live.

filter: drop-shadow(): Shape-Aware Shadows

Quick Answer

filter: drop-shadow(offset-x offset-y blur-radius color)generates a shadow based on the actual alpha channel of the element's rendered content — meaning it correctly follows the visible silhouette of a transparent PNG, an SVG icon, or any clipped/masked shape, rather than the element's rectangular box.

This is the shadow tool for anything irregular. Because it's a filter function rather than a box property, it can be chained with other filters (blur(), brightness(), saturate()) in a single declaration, and — critically — it operates on whatever is actually visible, transparency included. The CSS Filter Generator covers drop-shadow()alongside all eight other filter functions if you'd rather dial it in visually.

/* offset-x | offset-y | blur-radius | color — no spread value */.icon {  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.3));} /* Chain filters — drop-shadow works alongside blur, brightness, etc. */.hero-logo {  filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.25)) saturate(1.1);}

The bounding-box problem, illustrated

Picture a star-shaped icon saved as a transparent PNG. The image file itself is a square, but only the star shape inside it is opaque — the corners around the star are transparent. Applying box-shadow to that image draws a shadow around the full square, including the invisible corners, producing a shadow that looks nothing like a star. Applying filter: drop-shadow() instead traces only the opaque star pixels, producing a shadow that correctly matches the visible icon.

/* A transparent PNG star icon */ /* box-shadow — shadow is a plain rectangle, ignoring the   transparent corners around the star */.star-icon {  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);} /* filter: drop-shadow() — shadow hugs the star's actual   silhouette, transparent pixels included */.star-icon {  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.4));}

This is also why drop-shadow() is the right choice after clip-path or mask. Both of those properties only change what's painted, not the element's actual box — so box-shadow still shadows the pre-clip rectangle. drop-shadow() shadows what you actually see.

box-shadow vs. filter: drop-shadow(): Which to Use

Quick Answer

Default to box-shadow for boxes — cards, buttons, panels, modals — since it's cheaper to render at scale and respects border-radius natively. Reach for filter: drop-shadow() specifically when the content is transparent, irregularly shaped, an SVG, or has already been clipped/masked.

Situationbox-shadowdrop-shadow()
Card / panel / button✓ Best choiceWorks, costs more
Transparent PNG icon✗ Boxes the transparency✓ Best choice
SVG shape✗ Ignores the path✓ Best choice
Element with clip-path/mask✗ Shadows pre-clip box✓ Best choice
Many repeated elements (list of cards)✓ Cheaper at scaleMore expensive per element
Needs an inset shadow✓ Only option (inset)✗ No inset equivalent

The performance gap matters mainly at scale — a handful of drop-shadow() filters on hero icons or a logo is a non-issue on any device made in the last several years. The concern is real when you're shadowing dozens or hundreds of repeated elements (a card grid, a data table), where box-shadow's simpler box-based computation stays cheaper.

Frequently Asked Questions

What is the difference between box-shadow and filter: drop-shadow()?

box-shadow draws a shadow based on an element's box (respecting border-radius) but ignoring internal transparency. filter: drop-shadow() traces the actual alpha channel, so it correctly hugs the visible pixels of a transparent PNG, SVG, or clipped shape.

Can you use box-shadow on text?

No — use text-shadow instead, which paints behind the glyph outlines rather than a bounding box.

Can you have multiple box-shadows on one element?

Yes, as a comma-separated list. The first shadow paints on top. Stacking a tight dark shadow with a larger soft one is the standard way to build realistic elevation.

Is filter: drop-shadow() slower than box-shadow?

For a handful of elements the difference is negligible. At scale — many repeated shadowed elements — box-shadow stays cheaper because it computes from simple box geometry rather than the full alpha channel.

Why doesn't box-shadow follow clip-path or a transparent PNG?

Because clip-path, mask, and PNG transparency only change what's painted inside the box — they don't change the box itself, which is what box-shadow reads. filter: drop-shadow() reads the actual rendered pixels instead.

Is there a free CSS shadow generator?

Yes — CSSAWWWARDS has a Box Shadow Builder, a Text Shadow Generator, and a Filter Generator covering drop-shadow(). All free, no signup.

Share on XLinkedIn