Layered box-shadows in CSS: blur, spread and speed

What each box-shadow value does, how far a shadow really reaches, why five faint layers look better than one dark one, and how to animate shadows cheaply.

On this page

A good shadow is the difference between a card that floats and a card that looks pasted on. Most shadows that look wrong have one of three problems: too dark, too uniform, or cut off by a parent. This article explains each part of the box-shadow value with numbers, shows why stacking several faint layers looks more natural, and covers the performance side, which matters as soon as a shadow moves.

All the CSS below comes from the presets of our box shadow generator, and the reach figures were computed from them.

The five values

box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);

In order: x offset (0, positive moves right), y offset (10px, positive moves down), blur radius (30px), an optional spread (missing here, so 0) and the colour. An optional inset keyword draws the shadow inside the box instead.

The spread grows or shrinks the shadow's shape before blurring: 4px makes it 4 px larger on every side, -5px makes it 5 px smaller. The blur then softens the edge. The CSS specification describes it as a Gaussian blur with a standard deviation of half the blur radius, so the soft edge runs from roughly one blur radius inside the shape to one blur radius outside it.

How far a shadow reaches

That gives a simple rule for the visible reach on each side: blur + spread, plus the offset on the side it moves towards and minus it on the other side. For the shadow above:

SideCalculationReach
Top30 + 0 − 1020 px
Bottom30 + 0 + 1040 px
Left and right30 + 030 px

That is why a card with this shadow needs about 40 px of space below it before the next element, and why it gets clipped inside a container with overflow: hidden and little padding. A shadow never takes up layout space, so nothing moves out of its way.

A negative spread is how you stop a shadow showing on the sides. The "lifted" preset,

box-shadow:
  0 20px 25px -5px rgba(0, 0, 0, 0.1),
  0 8px 10px -6px rgba(0, 0, 0, 0.1);

reaches 0 px above the card (25 − 5 − 20), 20 px to each side and 40 px below. The light seems to come from above, and the card looks raised off the page.

Why layer several shadows

A real object's shadow is sharp and dark close to it and soft and faint further away. A single box-shadow can only be one of those. Several layers, each with a larger offset and blur, approximate both:

box-shadow:
  0 1px 1px rgba(0, 0, 0, 0.08),
  0 2px 2px rgba(0, 0, 0, 0.08),
  0 4px 4px rgba(0, 0, 0, 0.08),
  0 8px 8px rgba(0, 0, 0, 0.08),
  0 16px 16px rgba(0, 0, 0, 0.08);

Each layer is only 8% opaque, but where all five overlap, right under the edge of the card, they combine to 1 − 0.92⁵ ≈ 34%. Further out only the large layers remain, so the shadow fades smoothly over 32 px below the card and 16 px to the sides, with nothing above. The result has a crisp contact line and a long, soft falloff, which one layer at 34% cannot do: it would be uniformly dark and look heavy.

Layers are painted in order, the first on top. Put the small, sharp layer first.

Shadows that are not shadows

box-shadow is also a cheap way to draw things that have nothing to do with light:

  • A hard offset shadow, 6px 6px 0 rgb(31, 41, 55), with no blur, gives the flat, graphic "neo-brutalist" look.
  • A ring, 0 0 0 4px rgba(59, 130, 246, 0.35), is a spread with no blur and no offset: an outline that does not change layout, unlike border. Paired with a 24 px blur layer in the same colour it becomes a glow, often used for a focus state. If you use it for focus, make sure it has enough contrast against the background, or add a real outline.
  • An inset shadow, inset 0 2px 6px rgba(0, 0, 0, 0.25), makes a field look pressed in.

box-shadow or drop-shadow

box-shadow follows the element's box, rounded corners included. A PNG with a transparent background or an SVG icon still gets a rectangular shadow. filter: drop-shadow(...) follows the visible pixels instead, so a logo gets a logo-shaped shadow.

drop-shadow has no spread and no inset. The generator converts when it can: the soft, layered and hard presets have a drop-shadow equivalent (the layered one becomes five chained drop-shadow() functions), while the lifted and glow presets use a spread and the inset preset is inset, so the tool says there is no equivalent instead of producing something different.

Performance: what is cheap and what is not

A shadow is drawn during painting. A static shadow on a few cards costs almost nothing. The cost appears in three situations:

  1. Animating the shadow itself. Transitioning box-shadow from a small to a large value on hover means repainting the whole shadow area every frame. With large blur radii and many elements, that can drop frames on modest phones.
  2. Very large blurs on large elements. The blur work grows with the area covered.
  3. Shadows on hundreds of elements in a scrolling list.

The usual fix for a hover effect is to paint the larger shadow once, on a pseudo-element, and animate only its opacity, which browsers can usually handle without repainting:

.card { position: relative; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
.card::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.12); opacity: 0; transition: opacity 0.2s; }
.card:hover::after { opacity: 1; }

Common mistakes

  1. Pure black at high opacity. A single layer above about 25% opacity tends to look heavy and dirty. Start at 0.08 to 0.12 per layer and let the layers add up.
  2. Shadows on a dark background. A black shadow on a near-black page is invisible; use a lighter surface colour or a subtle border instead.
  3. Clipping. A parent with overflow: hidden cuts the shadow at its edge. Add padding at least equal to the reach.
  4. Mismatched light direction. If one card's shadow falls down and another's falls right, the page looks wrong without anyone knowing why. Keep x offsets at 0 or consistent.
  5. Using a shadow as the only sign of a state. A ring that is the only focus indicator must meet contrast requirements, and a hover lift should not carry meaning by itself.

Try the presets in the box shadow generator, paste an existing box-shadow value to edit it, and pair it with a background from the CSS gradient generator. For frosted and soft UI effects built on shadows, see glassmorphism and neumorphism accessibility.

Tools mentioned in this article

Back to the blog