How-To & Life · Guide · Developer Utilities
How to Use box-shadow in CSS
Shadow syntax (x, y, blur, spread, color), inset shadows, stacking multiple shadows, elevation systems, and dark-mode considerations.
The CSS box-shadow property is how modern interfaces communicate elevation: cards lift off the page, modals float above a dim backdrop, buttons press down on click, and hover states hint at interactivity. The syntax is more capable than most people realize — four numeric values, a color, an optional inset keyword, and the ability to stack multiple shadows on one element. Designing a shadow system for an interface pays compound interest: once you’ve defined an elevation scale, everything feels coordinated. This guide covers the syntax, common patterns, dark-mode pitfalls, and how to build a reusable shadow scale.
Advertisement
The five (or six) values
A box shadow is defined by up to six components:
box-shadow: <offset-x> <offset-y> <blur> <spread> <color> [inset]; box-shadow: 0 2px 8px 0 rgba(0,0,0,0.12);
- offset-x: horizontal offset. Positive moves shadow right.
- offset-y: vertical offset. Positive moves shadow down.
- blur: how soft the shadow is. 0 is hard-edged, larger values blur more.
- spread (optional): grows or shrinks the shadow before blurring.
- color: shadow color, almost always semi-transparent.
- inset (optional): draws the shadow inside the element instead of outside.
Light comes from above
Real-world shadows come from light, and in most UI conventions, light shines from above (or slightly above-left). So shadows cast downward and slightly right. Positive Y offset, zero or slightly positive X offset:
/* Natural card shadow */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.10);
Break this convention only for specific effects (inverted elevation, moody product photography). For general UI, stick with it or your interface feels off.
Subtle beats bold
Good UI shadows are surprisingly gentle. A 1–2 px offset, 4–12 px blur, and 5–15% black opacity cover most elevation needs. Heavier shadows (20+ px blur, 30%+ opacity) look dated and heavy; they were fashionable in the early 2010s and have since gone out of style.
/* Too heavy - 2010s style */
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
/* Modern subtle */
box-shadow: 0 1px 3px rgba(0,0,0,0.08),
0 4px 12px rgba(0,0,0,0.06);Stacking shadows for depth
Real shadows have a near-contact shadow (tight, dark) and an ambient shadow (wide, soft). Combining both with a comma-separated list feels more natural than a single shadow:
.card {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.06),
0 8px 24px rgba(0, 0, 0, 0.08);
}The first value produces the contact shadow right under the card; the second adds the ambient glow. Tools like Material Design and shadcn build whole systems around this two-shadow pattern.
Spread: the secret fourth number
Spread adds to or subtracts from the shadow’s rendered size before the blur is applied. Positive spread is useful for thick, buttery shadows on big elements; negative spread tightens the shadow so only the edges show.
/* Tight inner halo */ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); /* Lifted card */ box-shadow: 0 10px 20px -5px rgba(0,0,0,0.15);
The first pattern is the classic “focus ring” replacement for outline; the second lifts the shadow so it reads as more “off the page” than its vertical offset alone.
Inset shadows
The inset keyword draws the shadow inside the element. Uses:
- Pressed button state: subtle inset shadow makes the button look depressed when clicked.
- Input fields: inset shadow gives a sunken appearance.
- Panels in dashboards: inset creates the illusion of a recessed area.
.input {
box-shadow: inset 0 1px 2px rgba(0,0,0,0.08);
}
.button:active {
box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);
}Building an elevation scale
Rather than inventing shadows per element, define a scale and reference it. Material Design uses 24 levels (overkill); most systems do fine with 4–6. A pragmatic scale:
:root {
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow: 0 1px 3px rgba(0,0,0,0.08),
0 4px 12px rgba(0,0,0,0.06);
--shadow-md: 0 4px 8px rgba(0,0,0,0.10),
0 8px 24px rgba(0,0,0,0.08);
--shadow-lg: 0 12px 32px rgba(0,0,0,0.12),
0 4px 8px rgba(0,0,0,0.06);
--shadow-xl: 0 20px 40px rgba(0,0,0,0.18);
}Map UI concepts to levels:
- sm: input borders, subtle cards.
- base: default cards.
- md: hover states for cards, dropdown menus.
- lg: dialogs, popovers.
- xl: modals with full-screen backdrops.
Hover transitions
A card that lifts on hover is a classic interactive cue. Combine shadow with a tiny upward translate and a transition:
.card {
box-shadow: var(--shadow);
transition: box-shadow 150ms ease, transform 150ms ease;
}
.card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}The translate sells the physical lift; the shadow upgrade reinforces it. 150ms is the sweet spot — faster feels twitchy, slower feels laggy.
Dark mode considerations
Black shadows on black backgrounds are invisible. In dark mode, either increase opacity dramatically, use a slightly lighter-than-background shadow color, or rely on a subtle inner border instead of a shadow.
/* Light mode */
.card { box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
/* Dark mode */
@media (prefers-color-scheme: dark) {
.card {
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
/* Or: a subtle glow */
/* box-shadow: 0 0 0 1px rgba(255,255,255,0.06); */
}
}Many dark themes skip shadows entirely and use borders or background-color steps for elevation instead.
Colored shadows
Shadows don’t have to be gray. A subtle tint of the element’s color can add vibrancy:
.primary-button {
background: #3b82f6;
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4);
}The shadow matches the button color at low opacity, making the button glow slightly. Use sparingly — coloring every shadow reads as try-hard. Save for hero CTAs and accent elements.
Performance
Large-blur shadows on many elements can hurt scroll performance, especially on low-end devices. Shadows are rasterized and re-composited on every paint. For heavy UIs (long feeds with shadowed cards), consider:
- Using
will-change: box-shadowon elements that transition shadows, so the browser pre-promotes them to their own layer. - Replacing expensive shadows with cheaper borders or background gradients.
- Testing scrolling performance in DevTools’ Performance panel.
Common mistakes
Using offset-Y with zero blur and getting a hard-edged black line instead of a shadow — always include some blur. Picking shadow opacities in the 30–50% range that scream “2012.” Forgetting dark mode and leaving black shadows invisible. Stacking shadows on every nested element so the UI looks like a physical pile of business cards. Using box-shadow for focus rings and forgetting to preserve accessibility focus indicators when you remove outline — keyboard users rely on them. And the subtle one: letting shadows grow on hover but not animating them, so they pop instead of easing — always transition.
Run the numbers
Our box shadow generator lets you compose multi-layer shadows with live preview and copy-ready CSS. Pair with the border-radius generator for card styling and the gradient generator for backgrounds that complement the elevation.
Advertisement