CSS Box Shadow Guide: Creating Depth and Dimension
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;
| Value | Purpose | Required | Default | Example |
|---|---|---|---|---|
h-offset | Horizontal displacement | Yes | — | 4px shifts shadow right |
v-offset | Vertical displacement | Yes | — | 4px shifts shadow down |
blur | Edge softness | No | 0 | 12px creates a soft edge |
spread | Expand or contract shadow | No | 0 | -2px shrinks the shadow |
color | Shadow color | No | Browser default | rgba(0,0,0,0.15) |
inset | Shadow inside the box | No | outset | inset 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);
}
Multiple Shadows (Recommended)
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:
| Level | Shadow CSS | Use Case |
|---|---|---|
| 0 | none | Flat resting state |
| 1 | 0 1px 2px rgba(0,0,0,0.05) | Subtle card lift |
| 2 | 0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06) | Hovered cards |
| 3 | 0 4px 8px rgba(0,0,0,0.1), 0 12px 32px rgba(0,0,0,0.08) | Modals, popovers |
| 4 | 0 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 Overlay
.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.
| Concern | Impact | Recommendation |
|---|---|---|
Animating box-shadow | Medium | Animate opacity on a pseudo-element instead for smoother frames |
| Large blur radius (50px+) | High | Blur cost scales quadratically — keep blur under 40px |
| Many shadow layers | Medium | Limit to 3 layers per element |
| Shadows on scrolled content | Low | Browsers cache rasterized shadows |
will-change: box-shadow | Low | Only 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-shadowtakes up to six values: h-offset, v-offset, blur, spread, color, and the optionalinsetkeyword- 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.
Related Guides
- CSS Drop Shadow Tutorial — Compare
box-shadowandfilter: 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