CSS Box Shadow Guide: Creating Depth and Dimension

May 28, 20267 min read

What Is Box Shadow?

CSS box-shadow draws a shadow around an element's bounding box. It simulates elevation and depth without any image assets — a single declaration replaces the drop-shadow PNGs of an earlier era.

Apply box-shadow to cards, buttons, modals, inputs, tooltips, and any element that needs visual separation from the surface behind it.

Syntax Breakdown

box-shadow: h-offset v-offset blur spread color inset;
ValuePurposeRequiredDefaultExample
h-offsetHorizontal displacementYes4px shifts shadow right
v-offsetVertical displacementYes4px shifts shadow down
blurEdge softnessNo012px creates a soft edge
spreadExpand or contract shadowNo0-2px shrinks the shadow
colorShadow colorNoBrowser defaultrgba(0,0,0,0.15)
insetShadow inside the boxNooutsetinset flips direction

Always specify color with an alpha channel. Opaque black shadows look harsh on colored surfaces. Real shadows carry a tint of the surface underneath — rgba(15, 23, 42, 0.2) on a dark slate background looks far more natural than rgba(0, 0, 0, 0.2).

Understanding Each Value

h-offset and v-offset control position. Positive values push the shadow right and down. Negative values pull it left and up. Set both to 0 for a centered glow effect.

blur controls softness. A value of 0 produces a hard-edged shadow identical in shape to the element. A value of 20px diffuses it into a soft pool.

spread changes size. Positive spread enlarges the shadow beyond the element bounds. Negative spread contracts it — useful for preventing a shadow from extending past container edges.

inset flips the shadow inward. Inset shadows simulate pressed or concave surfaces like active button states and recessed input fields.

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

/* Inset shadow — pressed effect */
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);

Single vs. Multiple Shadows

A single shadow works for simple elevation. But it often looks artificial — real light produces two zones: a tighter shadow near the object and a softer, wider one farther away.

Single Shadow

.card {
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

Separate each shadow with a comma. The browser renders them in order — first shadow sits on top.

.card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.08),   /* tight contact shadow */
    0 4px 12px rgba(0, 0, 0, 0.12);   /* soft ambient shadow */
}

Layering two shadows produces a convincing depth effect. The first defines the immediate contact zone. The second simulates ambient light diffusion.

Realistic Layered Shadow Patterns

Material Design popularized the concept of elevation tiers — each level corresponds to a specific shadow configuration. You can build a similar system with five levels:

LevelShadow CSSUse Case
0noneFlat resting state
10 1px 2px rgba(0,0,0,0.05)Subtle card lift
20 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)Hovered cards
30 4px 8px rgba(0,0,0,0.1), 0 12px 32px rgba(0,0,0,0.08)Modals, popovers
40 8px 16px rgba(0,0,0,0.15), 0 24px 64px rgba(0,0,0,0.1)Full-screen overlays

Define these as CSS custom properties so every component references the same scale:

:root {
  --shadow-0: none;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-2: 0 2px 4px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.06);
  --shadow-3: 0 4px 8px rgba(0, 0, 0, 0.1), 0 12px 32px rgba(0, 0, 0, 0.08);
  --shadow-4: 0 8px 16px rgba(0, 0, 0, 0.15), 0 24px 64px rgba(0, 0, 0, 0.1);
}

.card { box-shadow: var(--shadow-1); }
.card:hover { box-shadow: var(--shadow-2); }
.modal { box-shadow: var(--shadow-3); }

Three-Layer Realism

For maximum control over the sharp-to-soft transition, use three layers:

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

The three-tier pattern mirrors physical observation: a crisp shadow directly beneath the object, a medium zone of partial occlusion, and a faint wash of ambient occlusion.

Inset Shadows

Inset shadows render inside the element border. They create the visual impression of a surface pushed inward — ideal for active buttons, recessed inputs, and carved-out containers.

/* Pressed button */
.button:active {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
  transform: translateY(1px);
}

/* Recessed input field */
.input-field {
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.12);
}

Combine inset and outset shadows for elements that both sink into the surface and cast a subtle outer shadow:

.input-group {
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, 0.12),
    0 1px 2px rgba(0, 0, 0, 0.05);
}

Common Shadow Designs

Card Lift on Hover

.card {
  transition: box-shadow 0.3s ease, transform 0.3s ease;
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.1),
    0 4px 12px rgba(0, 0, 0, 0.08);
}

.card:hover {
  transform: translateY(-2px);
  box-shadow:
    0 4px 8px rgba(0, 0, 0, 0.12),
    0 12px 32px rgba(0, 0, 0, 0.12);
}

Keep the transition duration between 200ms and 300ms for a snappy feel.

Neon Glow

.neon {
  background: #10B981;
  box-shadow:
    0 0 8px rgba(16, 185, 129, 0.6),
    0 0 24px rgba(16, 185, 129, 0.3),
    0 0 48px rgba(16, 185, 129, 0.15);
}

Neon effects use centered offsets (0 0) with colored shadows at decreasing alpha values. Three layers mimic real light falloff.

.modal {
  box-shadow:
    0 4px 8px rgba(0, 0, 0, 0.1),
    0 12px 32px rgba(0, 0, 0, 0.08),
    0 24px 64px rgba(0, 0, 0, 0.06);
  border-radius: 12px;
}

Modals sit at the highest elevation tier. Three layers ensure the panel separates clearly from the backdrop.

Performance Tips

Shadows are GPU-accelerated, but they carry a cost when misused.

ConcernImpactRecommendation
Animating box-shadowMediumAnimate opacity on a pseudo-element instead for smoother frames
Large blur radius (50px+)HighBlur cost scales quadratically — keep blur under 40px
Many shadow layersMediumLimit to 3 layers per element
Shadows on scrolled contentLowBrowsers cache rasterized shadows
will-change: box-shadowLowOnly add when actively animating

The biggest performance trap is animating box-shadow directly. The browser repaints the shadow on every frame. A smoother technique uses a pseudo-element:

.card {
  position: relative;
}

.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.card:hover::after {
  opacity: 1;
}

This approach animates opacity — a compositor-only property that runs at 60fps without repaints.

Key Takeaways

  • box-shadow takes up to six values: h-offset, v-offset, blur, spread, color, and the optional inset keyword
  • Layer two or three shadows — one tight, one soft — for realistic depth
  • Use alpha-channel colors so shadows blend with their background naturally
  • Define a shadow scale with CSS custom properties for elevation consistency
  • Inset shadows simulate concave or pressed surfaces
  • Limit shadow layers to 3 and blur to 40px to avoid performance issues

Try It Yourself

Build and preview box shadows in real time with our CSS Box Shadow Generator. Adjust offsets, blur, spread, and color — then copy the generated CSS straight into your project.

  • CSS Drop Shadow Tutorial — Compare box-shadow and filter: drop-shadow() to choose the right one for images and SVG
  • CSS Visual Effects — Combine shadows with gradients, glow, and glass morphism for modern UI design
  • CSS Design Tips — Practical techniques for pairing shadows with gradients and color