Tool · design

Box shadow.

Stack as many layers as your scene needs — every realistic shadow in production is two or more layers. Tune offset, blur, spread, colour, and alpha for each. Copy CSS that looks the way the design spec asked for it. Local; no upload.


Preview
Presets
Layers · 1
CSS
box-shadow: 0px 4px 12px 0px rgba(12, 16, 25, 0.12);

Offset, blur, spread, colour, inset.

The CSS box-shadow property takes up to six values: optional inset, then x-offset, y-offset, blur radius, spread, and colour. The order of x, y, blur, spread is fixed; the colour can come first or last. Inset moves the shadow inside the box (a sunken look) instead of outside (the default lifted look).

box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
/*           x  y blur color */

box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.16);
/* sunken effect — shadow inside the box */

Each parameter has a real-world meaning that matters when you're tuning a shadow:

  • x-offset moves the light source horizontally — positive values cast right.
  • y-offset moves the light source vertically — positive values cast down (the light is above).
  • blur controls softness. Higher = softer; 0 = hard-edged.
  • spread grows or shrinks the shadow before it gets blurred. Negative spread plus high blur = the "lifted" look.
  • colour and alpha determine the shadow's tone. Real shadows are never pure black; a slight tint toward the brand colour or an off-black at low alpha looks better.

Real shadows are at least two layers.

A single box-shadow looks computer-generated. Real shadows in real light have multiple components: a sharp shadow near the object plus a softer ambient shadow that extends further. Material Design's elevation system is built entirely around this — every "elevation" level has 2-3 layers stacked.

/* Material elevation 4 — three layers */
box-shadow:
  0 2px 4px -1px rgba(0, 0, 0, 0.20),
  0 4px 5px 0   rgba(0, 0, 0, 0.14),
  0 1px 10px 0  rgba(0, 0, 0, 0.12);

The first layer is the contact shadow — sharp, close, just below the element. The second is a mid-distance shadow that softens. The third is the ambient halo that spreads further out. Stack them and you get a shadow that reads as physical rather than synthesised.

The presets above include "Card" (2 layers) and "Material 4" (3 layers) as starting points. The "Lifted" preset uses negative spread on the larger blur to keep it close to the element while still spreading wide — a Tailwind shadow-xl trick.

Shadows are expensive to paint.

Every box-shadow requires the browser to draw the shadow into a separate buffer (the "shadow layer"), apply Gaussian blur to it, then composite it back. The cost is roughly proportional to (width × height × blur²). A modal with a box-shadow: 0 0 200px across a 1200×800 area can drop frame rate noticeably during animations.

Three optimisations:

  • Use transform for animations, not a moving shadow. Animating box-shadow directly forces a full repaint each frame; animating the element's transform with a static shadow stays GPU-cheap.
  • Cap blur at ~40px for visible-on-scroll elements. Beyond that, the visual difference is small but the cost is quadratic.
  • Fake huge shadows with images. A pre-rendered PNG with a soft drop shadow at large element scale is often faster to composite than a live shadow blur.

In Lighthouse audits, a heavy box-shadow on hero elements is one of the most common First Contentful Paint culprits. If your CWV scores are unexpectedly low and you can't find a JS bottleneck, check the shadows.

Inset for sunken; filter for irregular shapes.

Inset shadows draw inside the box outline. Use them for the impression that the element is recessed — input fields, code blocks, "press me" buttons in their pressed state. The math is identical to outset; just add the inset keyword.

/* "pressed" button look */
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.16);

For non-rectangular elements (icons, SVG paths, irregular shapes) box-shadow follows the rectangular outline, not the actual visible shape. The fix is filter: drop-shadow() — it traces the rendered alpha, so it shadows the visible shape correctly.

/* SVG icon with shadow following its outline */
.icon { filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); }

Trade-offs: filter: drop-shadow is cheaper than box-shadow on irregular content but more expensive on plain rectangles. Multiple drop-shadow filters compose by chaining (each one filters the result of the previous). Use box-shadow on rectangular elements; switch to filter for icons and paths.

Found this useful?