Shadow choice affects how real your UI feels. Pick the wrong one and icons look like rectangles with fuzzy edges. Pick the right one and stickers, badges, and odd shapes read as physical objects. By the end of this article you will know when to reach for filter: drop-shadow() and when box-shadow wins. You will build a clean card that uses box-shadow for depth and a sticker-style speech bubble that uses drop-shadow() to cast a silhouette-accurate shadow.
Why filter: drop-shadow() vs. box-shadow Matters
box-shadow paints from the element’s border box. It does not care if the content inside is transparent, clipped, or shaped. You get a rectangular shadow softened by blur and spread, rounded by border-radius if present. That makes box-shadow perfect for surfaces like cards, modals, and buttons.
filter: drop-shadow() uses the element’s pixels (its alpha mask). It examines the rendered shape and casts a shadow around the visible parts, including internal holes. This is the tool for icons with transparency, PNG stickers, inline SVG logos, and any non-rectangular shape. drop-shadow() can wrap around strokes and curves in a way box-shadow cannot.
Knowing the difference keeps you from forcing a rectangular shadow onto a circular avatar or a badge with a tail. Use each where it shines and your UI reads naturally.
Prerequisites
You only need basic layout skills. We will keep the markup lean and the CSS focused. A small variable theme will make tweaks easy.
Basic HTML
CSS custom properties
CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
The demo has two parts inside a wrapper. First, a card that uses box-shadow for soft elevation. Second, a “sticker” speech bubble rendered as inline SVG that uses filter: drop-shadow() to follow its silhouette. You can replace the SVG with a PNG or an icon font glyph and keep the same shadow principle.
Box-shadow Card
box-shadow casts from the element's box. Great for surfaces and containers.
drop-shadow() follows the bubble shape, including the tail.
Step 2: The Basic CSS & Styling
This foundation sets a neutral canvas, a few custom properties for color and shadow strength, and a simple layout that places the card next to the sticker. The variables make it easy to tweak both effects without hunting through selectors.
Advanced Tip: Treat shadows as tokens. The alpha values in –shadow-umbra, –shadow-penumbra, and –shadow-ambient let you scale an elevation system across components. Use the same approach for border radii and spacing to keep visual rhythm steady.
Step 3: Building the Box-shadow Card
The card needs a realistic multi-layer shadow and a light hover lift. Layering box-shadow gives you a convincing elevation because real-world shadows have an umbra (crisp core), a penumbra (soft feather), and ambient bounce. box-shadow is cheap to render and ideal for rectangular surfaces, including cards, sheets, and toast notifications.
box-shadow paints relative to the element’s border box. Set a border-radius and the shadow follows the rounded corners, which keeps the elevation consistent with the card’s shape. Multiple comma separated shadows create depth: a larger blur with lower alpha for ambient feel, a mid blur for penumbra, and a tighter one for umbra.
The hover lifts the card with a slight translateY so the shadow grows and shifts as it would on a surface. You can swap the values to fit your scale, but keep three layers to avoid a muddy blur halo.
If you were shading a circular avatar, box-shadow would still cast from the rectangular box that wraps the circle image. That is where drop-shadow() comes in. If your avatar is truly circular using CSS (see the guide on how to make a circle with CSS), you still get a bounding box shadow unless you clip or mask and then rely on drop-shadow() to honor the alpha.
Step 4: Building the Silhouette Shadow with filter: drop-shadow()
The SVG speech bubble has a triangular tail and curved edges. box-shadow would outline its rectangle and look wrong. filter: drop-shadow() evaluates the actual pixels and wraps the shadow around the contour, including the tail and any internal holes.
/* CSS */
.sticker__svg {
/* base size defined earlier */
display: block;
/* chain multiple drop-shadows for a richer look */
filter:
drop-shadow(0 10px 10px rgba(0, 0, 0, 0.18))
drop-shadow(0 2px 4px rgba(0, 0, 0, 0.12));
transform: rotate(-2deg);
transition: filter 140ms ease, transform 140ms ease;
/* filter creates a stacking context; this helps isolate blending and z-order */
will-change: filter, transform;
}
.sticker__svg:hover {
filter:
drop-shadow(0 14px 18px rgba(0, 0, 0, 0.22))
drop-shadow(0 4px 8px rgba(0, 0, 0, 0.14));
transform: rotate(0deg) translateY(-2px);
}
/* Dark backdrop tweak to pop the sticker caption a bit more */
.sticker__caption {
text-shadow: 0 1px 1px rgba(0,0,0,0.35);
}
How This Works (Code Breakdown)
filter: drop-shadow() samples the element’s alpha mask. The SVG path defines the bubble outline and the triangular tail in a single vector shape. The filter computes a shadow exactly around those opaque pixels. If you used a transparent PNG logo or an icon font glyph rendered to pixels, drop-shadow() would follow that silhouette as well.
You can stack multiple drop-shadow() functions. Unlike box-shadow, drop-shadow() ignores spread. You control softness with the blur parameter and richness by chaining several passes at different sizes and opacities. The slight rotation sells the “sticker” vibe by breaking the grid and giving the shadow a natural offset.
If you prefer a CSS-only speech bubble with a triangular tail made from borders, the parent’s drop-shadow() will not include the tail if the tail lives in a pseudo-element. The filter sees only the pixels of the element it is applied to. You would need to apply matching drop-shadow() to the pseudo-element or combine the shapes first. If you want a CSS bubble recipe, see this guide to a speech bubble shape, then decide where to place the filter for the final element that holds the full silhouette.
Shadows communicate elevation. Treat elevation as a state that you can animate subtly. Transition the blur, offset, and rotation together so the user reads the change as a physical lift instead of a color flash. Respect motion preferences to keep the interface calm for users who reduce motion.
This pattern keeps movement subtle and purposeful. The card shifts a few pixels. The sticker rotates toward neutral and deepens its shadow by a small margin. The animation entry softens into place to avoid a harsh snap.
Accessibility & Performance
Accessibility
Shadows are decorative, so do not let them interfere with semantics. The card is an article with a button. The sticker is a figure with a figcaption. The SVG uses a title for assistive tech. If a graphic carries no meaning, set aria-hidden=”true” on the SVG and skip the figcaption. If the bubble conveys status, supply a text alternative in the surrounding label or caption.
Consider focus states. Shadows can improve focus indication by adding an inner ring or an outer glow. For focus, stick to outlines or visible borders that meet contrast guidelines. Treat shadows as secondary support rather than the only focus indicator.
Honor motion preferences. The prefers-reduced-motion media query above removes the animation and transitions. Keep hover shadows responsive but restrained so users are not distracted when scanning content.
Performance
box-shadow is fast and works well even in large lists. You can animate offset and opacity cheaply. Avoid animating the blur radius at high values across dozens of elements, since that can trigger costly repaint work.
filter: drop-shadow() is heavier than box-shadow because the browser needs to rasterize and sample the alpha mask. Use it where you gain a clear shape benefit. Limit animated filter changes. A small hover deepening is fine. Large, continuous pulsing is costly. The filter creates a new stacking context, which can help isolate blending, but it also changes paint order. Test interaction layers like tooltips to make sure z-index still behaves as expected.
Pitfalls to watch for: drop-shadow() sees only the element’s own pixels. If your shape is built from multiple DOM elements or pseudo-elements, apply the filter to a wrapper that contains the completed silhouette, or use SVG where the whole path is a single shape. For card-like surfaces and layout boxes, stop fighting the rectangle and stick with box-shadow for predictable, cheap depth.
Note: If you build pointers with CSS triangles (often used for tooltips), box-shadow on the tooltip box will not include the triangle. You can shadow the triangle separately, or swap to a silhouette source such as SVG and use drop-shadow(). For a CSS-only pointer, this tooltip shape recipe will get you started.
The last closing paragraph
Shadows should match the geometry of your element. You now have a reliable rule: use box-shadow for surfaces, use filter: drop-shadow() for silhouettes. Build cards, badges, pointers, and stickers that read as physical and intentional. You now have the tools to choose the right shadow for every shape and to scale a clean elevation system across your UI.