The box-shadow property sounds simple — but once you start layering shadows, building card elevation systems, or trying to recreate that exact Figma drop shadow in CSS, the syntax gets complicated fast.
This guide covers everything from the basics to advanced multi-layer techniques, with copy-paste examples throughout.
The Basic Syntax
box-shadow: offset-x offset-y blur-radius spread-radius color;
Each part explained:
| Parameter | What it does |
|---|---|
offset-x |
Horizontal position. Positive = right, negative = left |
offset-y |
Vertical position. Positive = down, negative = up |
blur-radius |
Edge softness. 0 = crisp, higher = more diffuse |
spread-radius |
Expands (+) or contracts (−) the shadow before blur |
color |
Any valid CSS color, usually rgba() for opacity control |
A simple card shadow:
.card {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
Inset Shadows
Add the inset keyword to flip the shadow inside the element — useful for pressed button states and recessed input fields:
/* Pressed button */
.btn:active {
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.2);
}
/* Recessed input field */
input {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
Multiple Shadow Layers
You can stack multiple shadows with a comma-separated list. The first shadow is drawn on top (closest to the element):
.card {
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12), /* tight inner shadow for crispness */
0 6px 24px rgba(0, 0, 0, 0.08); /* wide outer shadow for ambient depth */
}
This two-layer pattern mimics how real shadows work — a bright direct light source creates a sharp inner shadow, while ambient light creates a soft outer glow.
The Four Elevation Levels
Here's a practical elevation scale for UI design:
/* Flat — default state */
.elevation-0 {
box-shadow: none;
}
/* Subtle — cards, sections */
.elevation-1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10), 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* Standard — hover state, dropdowns */
.elevation-2 {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 8px 24px rgba(0, 0, 0, 0.08);
}
/* Elevated — modals, dialogs */
.elevation-3 {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), 0 16px 48px rgba(0, 0, 0, 0.10);
}
Glow Effects
Set both offsets to 0 and use a saturated colour for a glow:
/* Green glow */
.btn-primary:focus {
box-shadow: 0 0 0 3px rgba(47, 133, 90, 0.4);
}
/* Stacked glow for stronger effect */
.notification-badge {
box-shadow:
0 0 8px rgba(229, 62, 62, 0.5),
0 0 24px rgba(229, 62, 62, 0.25);
}
Using box-shadow as a Border
box-shadow can replace a border without affecting layout — useful for focus rings and outlines:
/* Focus ring that respects border-radius */
button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
}
/* 2px green border without layout shift */
.selected-card {
box-shadow: 0 0 0 2px #2f855a;
}
Neumorphism
The neumorphic trend uses two shadows in opposite directions on a matching background:
/* Element background must match --surface */
:root {
--surface: #e0e5ec;
}
.neumorphic {
background: var(--surface);
box-shadow:
9px 9px 16px #b8bec7, /* dark shadow — bottom-right */
-9px -9px 16px #ffffff; /* light shadow — top-left */
}
/* Pressed state */
.neumorphic:active {
box-shadow:
inset 4px 4px 8px #b8bec7,
inset -4px -4px 8px #ffffff;
}
Animating box-shadow
Animating box-shadow directly triggers a repaint on every frame. For smooth hover transitions, it's still acceptable for most UI:
.card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
For high-frequency animations (scroll-tied effects), animate opacity on a ::after pseudo-element instead — this stays on the compositor layer.
box-shadow vs filter: drop-shadow()
box-shadow |
filter: drop-shadow() |
|
|---|---|---|
| Follows | Element's box | Actual rendered shape |
Supports inset
|
✅ | ❌ |
| Supports spread | ✅ | ❌ |
| Multiple layers | ✅ (comma list) | ❌ (wrap in multiple elements) |
| Best for | Buttons, cards, divs | Transparent PNGs, SVGs |
Converting Figma Shadows to CSS
Figma's shadow panel maps directly to CSS:
-
X →
offset-x -
Y →
offset-y -
Blur →
blur-radius -
Spread →
spread-radius -
Colour + opacity →
rgba()orhsla() -
Inner Shadow → add
insetkeyword
Quick Reference: Common Patterns
/* Soft card */
box-shadow: 0 1px 3px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.06);
/* Material elevation 2 */
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.08);
/* Modal / dialog */
box-shadow: 0 8px 32px rgba(0,0,0,0.15), 0 20px 64px rgba(0,0,0,0.10);
/* Focus ring */
box-shadow: 0 0 0 3px rgba(66,153,225,0.5);
/* Inset (pressed button) */
box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);
/* Neumorphic (on #e0e5ec background) */
box-shadow: 9px 9px 16px #b8bec7, -9px -9px 16px #ffffff;
/* Remove shadow */
box-shadow: none;
Building Shadows Without Guessing
If you'd rather adjust sliders than type raw CSS, the CSS Box Shadow Generator at SnappyTools gives you a live preview with multi-layer support, Figma-style controls, and a one-click copy button. No install, runs 100% in the browser.
The key insight with box-shadow is that single-layer shadows look flat. Stack a tight, slightly opaque shadow with a wider, more transparent one — and you get depth that actually looks right.
Top comments (0)