A single box-shadow declaration produces a shadow that looks functional but rarely looks polished. If you've ever wondered why shadows in high-quality design systems look better than the ones you write from scratch, the answer is almost always multiple layers. Understanding how CSS handles layered shadows changes how you approach the property entirely.
The Basic Syntax
The box-shadow shorthand takes up to six values:
box-shadow: offset-x offset-y blur-radius spread-radius color;
With an optional inset keyword at the front to flip the shadow inside the element:
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
The only required values are offset-x and offset-y. Everything else has defaults: blur is 0 (sharp edge), spread is 0 (same size as element), color is the current color value.
A typical card shadow looks like this:
.card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
That's a downward shadow with moderate blur and 10% black. It works. It looks like a shadow. But it doesn't look like a physical object has depth.
Why Single Layers Look Flat
Real-world shadows have two components: a diffused ambient shadow that spreads softly in all directions, and a sharper directional shadow that comes from the light source. When you write a single box-shadow, you're choosing one or the other. If you choose a tight, low-blur shadow, it looks sharp but has no ambient softness. If you choose a wide, high-blur shadow, it looks soft but loses the sense of close proximity.
Good design systems layer both. The first layer handles the close, sharp shadow near the element's edge. The second layer handles the diffused ambient shadow that extends further out. Together they create the impression of an object with real depth sitting on a surface.
The Material Design elevation system is the most commonly referenced example. A card at elevation 2 uses two shadow layers: a shorter, less diffused shadow for the umbra (the dark core of the shadow) and a taller, more diffused shadow for the penumbra (the soft outer edge). The combination reads as natural lighting rather than a CSS effect.
Writing Multi-Layer Shadows
Multiple shadow layers are comma-separated in a single box-shadow declaration. Each layer is evaluated independently and composed on top of each other:
.card {
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 2px 8px rgba(0, 0, 0, 0.08);
}
The first layer is the tight, sharp near-shadow. The second is the wider, softer ambient shadow. Combined, the card appears to float slightly above the surface.
For more pronounced elevation:
.modal {
box-shadow:
0 4px 6px rgba(0, 0, 0, 0.07),
0 10px 20px rgba(0, 0, 0, 0.05),
0 20px 40px rgba(0, 0, 0, 0.04);
}
Three layers here: a tight shadow, a medium shadow, and a wide ambient shadow. The opacity decreases with each layer because wider shadows spread the darkness over a larger area. If all three layers had the same opacity, the result would be too dark.
The stacking order matters: layers listed first render in front. In practice, this is only visible at the edges where layers overlap at different blur radii.

Photo by Steve A Johnson on Pexels
The Spread Parameter
Spread is the most misunderstood parameter. It grows or shrinks the shadow before blur is applied. Positive spread makes the shadow larger than the element; negative spread makes it smaller.
Negative spread is particularly useful for inset shadows and for creating the illusion that a shadow is tucked under the edge of an element:
.card {
box-shadow:
0 2px 4px -1px rgba(0, 0, 0, 0.2),
0 6px 12px -2px rgba(0, 0, 0, 0.1);
}
The -1px and -2px spreads pull the shadows in slightly, preventing them from extending beyond the card's sides. This creates a more natural look for cards that sit on backgrounds close in color to the shadow itself.
Color Choices Beyond Black
Most tutorials default to rgba(0, 0, 0, alpha) for shadows, but black shadows on colored backgrounds look disconnected. A shadow should look like a darkened version of the background, not a floating black shape.
One approach is to use a dark desaturated version of the element's dominant color. A card with a blue tint looks better with a blue-shifted shadow:
.card-blue {
background: #dbeafe;
box-shadow:
0 2px 8px rgba(59, 130, 246, 0.15),
0 1px 3px rgba(59, 130, 246, 0.1);
}
Another approach is to use currentColor or a CSS variable tied to the design system's shadow color, which can shift per theme. Either way, the visual result is more integrated than a pure black shadow.

Photo by Irina Alekseevskaya on Pexels
Inset Shadows for Pressed States
inset shadows go inside the element's border-box rather than behind it. They're useful for pressed button states, input field focus indicators, and cut-out effects.
.button:active {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}
.input:focus {
box-shadow: inset 0 0 0 2px #3b82f6;
}
The second example uses inset with zero offsets and zero blur, which creates a solid interior border. This is a common way to add a focus ring without affecting layout the way border does.
Inset and external shadows can be combined in the same declaration, separated by commas, as long as inset is the first value of its layer.
Debugging Shadow Issues
The most common problems with box-shadow:
Shadow hidden by overflow: hidden on a parent. Box shadows are outside the element's box and get clipped. Fix by applying the shadow to a wrapper element that doesn't have
overflow: hidden.Shadow covered by a sibling element with a higher z-index. Add
z-indexto the element with the shadow, or restructure the stacking context.Shadow looks wrong at element edges. Usually a spread value that's too high or a blur that's hitting the element boundary. Adjusting spread to a small negative value often helps.
MDN Web Docs has the full box-shadow specification with details on how layers are composited. CSS-Tricks has articles on common shadow patterns in design systems.
Building Shadow Values
Figuring out the right multi-layer values through trial and error takes significant time. A visual generator lets you add layers, adjust each one independently, and see the result instantly.
EvvyTools has a CSS Generator that handles multi-layer box shadows alongside gradients and glassmorphism. You can build a multi-layer shadow stack in the generator, copy the output, and apply your naming conventions from there.
The CSS effects guide walks through the shadow generator in the context of a broader CSS effects workflow, including how shadow values interact with glassmorphism effects.
For performance reference, web.dev covers which CSS properties trigger repaint and when box-shadow changes affect compositing. Animating box-shadow directly triggers repaint; using opacity and transform on a pseudo-element shadow is the performance-safe alternative for animated shadows.
Once you have the right shadow values, storing them in CSS custom properties makes future updates simple. Define --shadow-card and --shadow-modal at the root level, reference them in components, and update both in one place when the design evolves. Multi-layer shadows look complex but are easy to maintain when the values live in one location and every component references the variable name rather than repeating the raw CSS. Shadow scales in established design systems like Material Design follow exactly this pattern.
Top comments (0)