Skip to content

Design

CSS Box-Shadow Explained: Syntax, Blur, Spread and Performance

9 min read

You copy a shadow from some site's generator, paste it into your stylesheet, and the result looks nothing like the preview you just saw. The blur is enormous, the element appears to float ten pixels off the page, and adding a second shadow makes the whole thing vanish. The problem is not box-shadow being hard — it is that most developers have never been told what each of the five values actually controls. This guide breaks the property apart, explains blur radius versus spread, shows how layered shadows work, covers accessibility pitfalls and performance trade-offs, and ends with a tool that removes the guesswork entirely.

The five values behind every box-shadow

A complete box-shadow declaration looks like this:

box-shadow: 4px 6px 12px 0 rgba(0, 0, 0, 0.25);

Read it left to right. 4px is the offset-x — how far the shadow shifts horizontally. 6px is the offset-y — how far it shifts vertically. These two are the minimum: without them the shadow sits directly behind the element, perfectly centred, which is rarely useful. 12px is the blur-radius. It controls how much the shadow is softened — it does not move the shadow, it feathers the edges. 0 is the spread-radius. A positive value expands the shadow beyond the element's box; a negative value shrinks it. And rgba(0, 0, 0, 0.25) is the shadow color with 25% opacity.

There is an optional sixth value: the inset keyword. When present, the shadow is drawn inside the element instead of outside. Inset shadows are the standard way to create inner depth — input fields, pressed buttons, sunken cards. Omit the keyword and every shadow falls on the outside by default.

Blur radius versus spread radius: the confusion that costs hours

These two values are the most commonly swapped, and the visual difference is dramatic. Blur-radius does not make the shadow bigger or smaller — it controls how sharply the shadow edge fades to transparency. A blur of 0 produces a hard-edged shadow identical to the element's silhouette. A blur of 20px creates a wide, soft gradient that bleeds far beyond the element. The browser implements this as a Gaussian blur on the shadow bitmap before compositing it under the element.

Spread-radius, on the other hand, is a direct geometric operation. It inflates or deflates the shadow rectangle before any blur is applied. A spread of 4px makes the shadow 4px larger on every side; a spread of -4px makes it 4px smaller. The practical effect: spread controls how visible the shadow is in the first place, while blur controls how soft it looks. A common trick for a subtle raised card is to keep blur moderate and spread at zero or slightly negative — this avoids the "big dark halo" that comes from a large blur with no spread adjustment.

The missing fourth value trap

When you write three values — like box-shadow: 0 4px 8px rgba(0,0,0,0.15) — the third number is the blur, not the spread. The spread defaults to 0. If you actually intended to set spread to 8px and blur to something else, the shorthand is wrong and the shadow will not match your design. Always verify the order: offset-x, offset-y, blur, spread.

Layered shadows: one property, multiple values

CSS lets you stack multiple shadows on the same element by separating them with commas. Each layer renders from first to last — the first shadow is on top, and each subsequent shadow sits behind it. This is how realistic depth effects are built: a single shadow looks flat and artificial, but two or three carefully tuned layers create the nuanced shading that our eyes associate with real objects.

.card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.07),
    0 4px 8px rgba(0, 0, 0, 0.12),
    0 12px 24px rgba(0, 0, 0, 0.08);
}

In this example, the first shadow is a tight, low-opacity accent directly below the card — it simulates the contact shadow where the surface meets the table. The second is a mid-range soft spread that creates the main sense of elevation. The third is a wide, faint wash that implies overall ambient light. The result reads as natural depth rather than a flat gray smear.

A second common pattern is layering a sharp shadow with a blurred one. The sharp layer (low or zero blur, small negative spread) creates a defined edge, while the soft layer fills in the ambient diffusion. Material Design uses exactly this approach: a short hard shadow plus a wider soft shadow produces the paper-lift effect that looks correct at every zoom level.

Common mistakes and how to fix them

The most frequent mistake is using a blur value that is far too large relative to the element. A button that is 36px tall should not have a 60px blur — the result is a diffuse cloud that detaches the element from the surface entirely. As a rough rule of thumb, keep blur between 0.5× and 2× the element's shorter dimension for natural-looking elevation.

The second mistake is forgetting about the implicit second layer. When a design calls for "the shadow on the design file", it often looks like it does because the designer stacked two or three box-shadows in Figma or Sketch. Copying only the topmost visible shadow produces a shadow that looks too sharp or too dark. Always check whether the mockup uses layered shadows — it almost always does.

A third pitfall is using filter: drop-shadow() interchangeably with box-shadow. They are not the same. Box-shadow follows the element's rectangular border-box, while drop-shadow respects alpha transparency — so an element with a transparent background and a rounded SVG icon will get a drop-shadow that traces the icon shape. But drop-shadow cannot do spread-radius, cannot do inset, and performs worse in long lists.

  • Huge arbitrary blur: keep blur proportional to the element size; 4px–16px covers most UI cards.
  • Single-layer flatness: stack at least two shadows — a tight contact layer and a wider ambient layer.
  • Offset too large: offsets above 8–10px make the element look detached; keep small for realistic elevation.
  • Forgetting inset: pressed buttons and input fields usually need inset shadows, not outside shadows.

Accessibility: shadowed text and contrast

Box-shadow on a container is generally safe for accessibility — it does not affect text contrast ratios. But text-shadow, which works similarly, can reduce readability if overused. WCAG 2.1 does not directly regulate text-shadow, but the contrast requirement between text and its adjacent background still applies. A text-shadow with the same color as the background creates a halo effect that can blur letterforms for users with low vision.

The practical rule: keep text-shadow blur small (1px–2px), use low opacity (under 0.3), and never rely on a shadow as the only means of separating text from a busy background. If the background is an image or gradient, the shadow is decorative — you still need sufficient contrast between the text color and the background's darkest/lightest underlying tone. When in doubt, run the element through a contrast checker with the background visible behind the shadow.

Shadows on interactive elements

Hover and focus states that increase shadow intensity (larger blur or higher opacity) improve perceivability for users who rely on visual feedback. This is not strictly required by WCAG, but it is a widely recommended practice that also benefits keyboard navigation: the shadow grows when the element receives :focus-visible, giving a clear affordance without relying solely on the browser's outline.

Performance: box-shadow versus filter versus images

Box-shadow is a CSS property that browsers rasterize when painting the element's layer. On modern hardware, a handful of box-shadows have negligible cost. The performance concern appears in two scenarios: long scrollable lists with dozens of shadowed items, and shadows on elements with many children that trigger frequent repaints during animation.

TechniqueSpread supportInset supportRespects alphaGPU-friendlyBest for
box-shadowYesYesNo (paints border-box)GoodCards, buttons, containers
filter: drop-shadow()NoNoYes (follows alpha)ModerateIcons, images with transparent backgrounds
Pre-rendered PNG shadowVia imageN/AYesN/A (raster)Complex decorative shadows on static elements
text-shadowNoN/AYesGoodHeadline glow, subtle text depth

The general guidance: use box-shadow for UI components, filter: drop-shadow() when you need alpha-traced shadows on images or icons, and avoid pre-rendered shadow images unless the visual is truly complex and static. Animating box-shadow is possible but triggers a repaint on every frame; for smooth 60fps animation, consider combining transform: translateZ(0) or will-change: transform to promote the element to its own compositor layer, or use the shadow on a pseudo-element that is independently promoted.

The box-shadow generator on Convrs

Convrs offers a box-shadow generator that removes the guesswork from shadow creation. Adjust offset-x, offset-y, blur, spread, and color with sliders while seeing a live preview of the result on different background colors. Add and reorder layers to build realistic depth. Every change updates the CSS output instantly — copy it straight into your stylesheet. The tool runs entirely in your browser; no shadow values or previews are sent to any server.

Buy me a coffee