← Blog
Web Design9 min

How CSS box-shadow Works: Offset, Blur, Spread, Inset

Master every parameter of <code>box-shadow</code>, layer multiple shadows for depth, and understand elevation design systems.

Syntax Breakdown

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

      /* Example */
      box-shadow: 4px 8px 16px 0px rgba(0, 0, 0, 0.15);
  • offset-x: Horizontal shift. Positive moves right, negative moves left.
  • offset-y: Vertical shift. Positive moves down, negative moves up.
  • blur-radius: How blurry the shadow edges are. 0 = hard edge. Required to be ≥ 0.
  • spread-radius: Expands (+) or contracts (−) the shadow before blurring. Optional, defaults to 0.
  • color: The shadow color. Use rgba() for transparency.
  • inset: Makes the shadow render inside the element rather than outside.

Blur vs Spread

Blur and spread are often confused. Blur softens the edges — higher values create a more diffuse, atmospheric shadow. Spread changes the size of the shadow shape before blurring — a positive spread makes the shadow larger than the element; a negative spread makes it smaller.

/* Same offset and color, different blur and spread */
      box-shadow: 0 4px 0px 0px #000;   /* hard shadow, no blur */
      box-shadow: 0 4px 8px 0px #000;   /* soft shadow */
      box-shadow: 0 4px 8px 4px #000;   /* soft shadow, expanded */
      box-shadow: 0 4px 8px -4px #000;  /* soft shadow, contracted */

Multiple Shadows

You can layer multiple shadows with a comma-separated list. They render front-to-back — the first shadow is on top.

Layered shadow for depth

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

This technique produces a more natural shadow that tapers off with distance, simulating real light behavior.

Inset Shadows

The inset keyword makes the shadow appear inside the element, creating a pressed or recessed effect.

/* Pressed button effect */
      .button:active {
        box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.2);
      }

      /* Neumorphic well */
      .input {
        box-shadow:
          inset 3px 3px 6px #d1d1d1,
          inset -3px -3px 6px #ffffff;
      }

Elevation Systems (Material Design Pattern)

Material Design defines a set of elevation levels, each with a specific shadow. This creates a consistent visual hierarchy across components:

:root {
        --shadow-1: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
        --shadow-2: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
        --shadow-3: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
        --shadow-4: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
      }

Performance Considerations

Box shadows trigger paint (not just composite) when they change during animation. To animate shadows smoothly:

  • Animate opacity of a pseudo-element with a shadow instead of the shadow itself
  • Or use filter: drop-shadow() which is hardware-accelerated via composite
  • Avoid animating spread-radius — it is especially expensive to repaint
/* Performant shadow animation using opacity */
      .card::after {
        content: '';
        position: absolute; inset: 0;
        box-shadow: 0 20px 40px rgba(0,0,0,0.3);
        opacity: 0;
        transition: opacity 0.2s ease;
      }
      .card:hover::after { opacity: 1; }

Common Mistakes

  • Using pure black: rgba(0,0,0,0.15) looks more natural than #000000 with opacity
  • Too many shadows: More than 3 layers rarely adds visible benefit and increases paint cost
  • Ignoring dark mode: Dark-mode designs typically replace box shadows with subtle borders — shadows disappear on dark backgrounds