figro
FigroGuides › How CSS box-shadow works
Web Design

How CSS box-shadow Works (with Examples)

By the Figro team · Updated July 2026 · about a 6-minute read

The CSS box-shadow property draws one or more shadow layers behind (or inside) any HTML element. It takes up to six values: an optional inset keyword, horizontal offset, vertical offset, blur radius, spread radius, and color. Because shadows render entirely in the browser without any image assets, they are zero-cost to change and work on any element from a button to a full page card.

The full syntax at a glance

The formal syntax for a single shadow layer looks like this:

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

Every value in square brackets is optional — only the two offsets and the color are required. Multiple shadow layers are separated by commas:

/* Single shadow */
box-shadow: 4px 4px 12px rgba(0, 0, 0, 0.15);

/* Two layers stacked */
box-shadow:
  0 1px 3px rgba(0, 0, 0, 0.12),
  0 4px 16px rgba(0, 0, 0, 0.08);

When the browser renders multiple layers, it draws them in order — the first item in the list appears on top, the last at the bottom.

What each parameter does

ParameterRequired?What it controls
insetNoMoves the shadow inside the element instead of behind it
offset-xYesHorizontal shift; positive moves right, negative moves left
offset-yYesVertical shift; positive moves down, negative moves up
blur-radiusNo (default 0)How soft the edges are; higher values mean more diffuse shadows
spread-radiusNo (default 0)Expands or shrinks the shadow before blurring; can be negative
colorYes*Any CSS color value; rgba() gives you opacity control

*Technically optional in the spec — browsers fall back to currentColor — but omitting it produces inconsistent results across browsers. Always specify the color explicitly.

Blur vs. spread: the distinction that trips people up

These two parameters are the most commonly confused, because both make a shadow look "bigger." They do different things:

A useful mental model: think of the shadow as a copy of the element's bounding box, shifted by the offsets, then grown or shrunk by the spread, then blurred. The result is composited behind the element.

The inset keyword

Adding inset as the very first token flips the shadow from behind the element to inside it. This is the standard way to create pressed-button effects, recessed panels, and inner glow.

/* Normal outward shadow */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);

/* Inset shadow — shadow appears inside the element */
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.15);

Inset shadows obey the same offset rules: positive offset-x shifts the shadow right inside the element, positive offset-y shifts it down. A common pattern is a small inset shadow on the top of a button's resting state and a larger one when the button is :active, simulating physical depth.

Multi-layer shadows for realistic depth

Real-world light rarely produces a single uniform shadow. Most polished UI components stack two or three shadow layers to create a more convincing sense of elevation. The typical approach is:

/* Layered shadow for a floating card */
.card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.10),   /* contact shadow */
    0 8px 24px rgba(0, 0, 0, 0.08);  /* ambient shadow */
}

Google's Material Design uses this technique systematically — each elevation level has a characteristic pair of shadows that together communicate how high the surface is above the page. You do not need to copy their exact values, but the two-layer pattern translates well to any design system.

Color tip: pure black shadows at even low opacity can look harsh against colored backgrounds. Try tinting the shadow color toward the background hue — for example, rgba(30, 60, 120, 0.12) on a blue card — for a more cohesive result.

Common practical patterns

Soft card lift

.card {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
}

Glowing focus ring (replaces outline)

button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.5);
}

Here both offsets are 0, blur is 0, and spread is 3px — the result is a solid ring around the element, which is a clean way to build custom focus indicators. Using box-shadow instead of outline means the ring respects border-radius.

Neumorphic surface

.neu-button {
  box-shadow:
    6px 6px 12px rgba(0, 0, 0, 0.12),
    -6px -6px 12px rgba(255, 255, 255, 0.9);
}

Neumorphism stacks a dark shadow on one side with a light (near-white) shadow on the opposite side, creating the illusion of a surface extruded from the background. It works best when the element background color matches the page background closely.

Performance and browser support

box-shadow is supported in every browser in use today — it has been part of CSS3 since before Internet Explorer 9. For animation performance, the browser has to repaint the shadow on every frame when you transition it, which can be expensive on mobile. The standard recommendation is to animate opacity or transform instead of box-shadow values directly, and use a pseudo-element or layered elements if you need smooth shadow transitions at high frame rates.

For most static or hover-triggered shadows, this is not a practical concern. Only optimize if you observe dropped frames in DevTools performance profiling.

Build your shadow visually

Figro's CSS box-shadow generator lets you drag sliders for every parameter — offset, blur, spread, opacity, color — and watch the shadow update in real time. Add multiple layers, choose from common presets, and copy the finished CSS, React, or Tailwind syntax instantly. Free, no signup, everything runs in your browser.

Open the free CSS box-shadow generator →

Figro's guides are educational and independent. They are not design or engineering advice. Some pages include affiliate links; if you purchase through them we may earn a commission at no extra cost to you.