Toolverse

CSS Box-Shadow: Syntax, Layering, and Performance

7 min read

The CSS box-shadow property is one of the most used visual effects on the web. According to the HTTP Archive's 2023 Web Almanac, box-shadow appears on 83% of all websites analyzed — more than border-radius or text-shadow. Yet most developers copy-paste shadow values without understanding the parameters behind them.

The Box-Shadow Syntax

The box-shadow property accepts up to six values in a specific order, defined in the CSS Backgrounds and Borders Module Level 3 (W3C specification):

box-shadow: [inset] <offset-x> <offset-y> [blur-radius] [spread-radius] <color>;

/* Example */
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.25);
  • offset-x — Horizontal displacement. Positive values move the shadow right; negative values move it left.
  • offset-y — Vertical displacement. Positive values move the shadow down; negative values move it up.
  • blur-radius — Controls the Gaussian blur applied to the shadow. A value of 0 produces a sharp edge. The blur algorithm doubles the specified radius — a 10px blur extends the shadow by 10px in each direction.
  • spread-radius — Expands or contracts the shadow. A positive value makes the shadow larger than the element; negative values shrink it. Useful for creating tight, focused shadows.
  • color — Any valid CSS color. Using rgba() or hsla() with transparency produces the most natural-looking shadows.
  • inset — Optional keyword that moves the shadow inside the element, creating a recessed or pressed appearance.

Layering Multiple Shadows

CSS supports comma-separated shadow values on a single property declaration. Layered shadows create more realistic depth effects than a single shadow because real-world light produces multiple penumbras at different distances.

/* Layered shadow for realistic elevation */
box-shadow:
  0 1px 2px rgba(0, 0, 0, 0.07),
  0 2px 4px rgba(0, 0, 0, 0.07),
  0 4px 8px rgba(0, 0, 0, 0.07),
  0 8px 16px rgba(0, 0, 0, 0.07);

Each layer uses progressively larger offsets and blur values with low opacity. The combined effect mimics soft ambient light found in Material Design and modern UI frameworks. Google's Material Design specification uses exactly this approach, defining 24 elevation levels with layered shadows.

Inset Shadows for Depth

The inset keyword flips the shadow inside the element. This technique is commonly used for:

  • Input fields — A subtle inset shadow makes text inputs feel recessed, signaling interactivity.
  • Pressed button states — Combining an inset shadow on :active with an outset shadow on the default state creates a convincing button press effect.
  • Container wells — Content areas with inset shadows visually separate sections without hard borders.

Performance Considerations

Shadows are rendered during the paint phase of the browser rendering pipeline. Each shadow layer adds computational cost, but modern GPU-accelerated browsers handle 2-4 layers without measurable impact. Specific performance notes:

  • Blur radius is the expensive parameter — Large blur values (50px+) on many elements can cause jank during scrolling. If animating shadows, transition between pre-computed values rather than interpolating blur.
  • Avoid animating box-shadow directly — Transitioning box-shadow triggers repaint on every frame. Instead, use a pseudo-element (::after) with the target shadow and animate its opacity — opacity changes are composited on the GPU.
  • drop-shadow() filter is different — The filter: drop-shadow() function respects the element's alpha channel (useful for PNGs), but does not support spread or inset parameters.

Common Shadow Patterns

Several shadow recipes appear consistently across design systems:

  • Subtle card elevation 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)
  • Floating modal 0 25px 50px rgba(0,0,0,0.25)
  • Sharp bottom border effect 0 2px 0 0 #e5e7eb (zero blur, zero spread)
  • Glow effect 0 0 20px rgba(59,130,246,0.5) (zero offset, large blur, colored)

Key Takeaways

  • Use rgba() colors with low opacity (0.1–0.3) for natural-looking shadows.
  • Layer 2-4 shadows with increasing blur for realistic depth — single shadows look flat.
  • Animate shadow opacity on pseudo-elements instead of transitioning box-shadow directly to avoid repaint costs.
  • Use inset shadows for interactive states (pressed buttons, input fields) rather than visual decoration.

Ready to design your own shadows? Use our CSS Box Shadow Generator to build layered shadow effects with real-time preview and copy the CSS directly into your project.

Try it yourself

Put what you learned into practice with our free tool.

Open Tool

Frequently Asked Questions

How many shadow layers can you use in CSS?
There is no specification limit on the number of comma-separated shadow layers. In practice, 2-4 layers produce realistic depth effects. Modern GPU-accelerated browsers handle this without measurable performance impact, though large blur values (50px+) on many elements can cause jank.
What is the difference between box-shadow and drop-shadow?
box-shadow applies to the element's rectangular box and supports spread-radius and inset. The filter: drop-shadow() function respects the element's alpha channel (useful for transparent PNGs and SVGs) but does not support spread or inset parameters.
Should I animate box-shadow for hover effects?
Avoid transitioning box-shadow directly — it triggers repaint on every frame. Instead, place the target shadow on a pseudo-element (::after) and animate its opacity. Opacity changes are composited on the GPU, resulting in smooth 60fps transitions.