What is box-shadow layering?
The CSS box-shadow property accepts a comma-separated list of shadow definitions. Each shadow layer has its own x-offset, y-offset, blur radius, spread radius, and color. By stacking multiple layers — for instance, a sharp close shadow combined with a soft distant shadow — you can simulate realistic lighting that a single shadow can't achieve. This technique is used by design systems like Material Design's elevation scale and Tailwind's shadow utilities.
In the physical world, shadows are never a single uniform blur. An object sitting on a surface has a tight, dark "contact shadow" right at its base, plus a softer, broader "ambient shadow" cast by the surrounding light. Replicating this in CSS requires layering: the first shadow provides the sharp edge contact, and additional layers add the softer, more diffused glow. Some advanced designs use three or more layers, adding subtle colored tints or inset highlights to simulate light reflecting off nearby surfaces.
The order of shadows in the comma-separated list matters. The first shadow in the list is rendered on top (closest to the viewer), and each subsequent shadow is painted behind the previous one. This stacking order lets you combine a small, dark, sharp shadow with a large, light, diffused shadow without the two interfering. Understanding this rendering order is key to composing convincing depth effects.
How to use this tool
Each layer card in the controls panel has sliders for X offset (horizontal displacement), Y offset (vertical displacement), blur radius (softness), and spread radius (expansion or contraction). Click the color input to choose a shadow color — use an RGBA or HSLA color with transparency for more realistic results, since real-world shadows are never fully opaque. Toggle the "Inset" checkbox to create inner shadows that appear inside the element's boundaries.
Add new layers with the "+ Add Shadow Layer" button at the bottom, or remove layers with the x button on each card. The white preview box updates instantly as you adjust values, so you can fine-tune the visual result without switching between code and browser. When you are satisfied, copy the Direct CSS output for a ready-to-paste box-shadow declaration, or use the CSS Variables format to integrate the shadow values into your design token system.
Practical examples
Material Design elevation scale (levels 1-5)
Material Design defines elevation levels using layered shadows that increase in offset, blur, and transparency as the element rises higher. Here is a simplified version of levels 1 through 5 that you can use as a starting point for your own elevation system.
/* Elevation 1 - Cards at rest */
.elevation-1 {
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
}
/* Elevation 2 - Raised cards */
.elevation-2 {
box-shadow:
0 3px 6px rgba(0, 0, 0, 0.15),
0 2px 4px rgba(0, 0, 0, 0.12);
}
/* Elevation 3 - Floating action buttons */
.elevation-3 {
box-shadow:
0 10px 20px rgba(0, 0, 0, 0.15),
0 3px 6px rgba(0, 0, 0, 0.10);
}
/* Elevation 4 - Navigation bars, dialogs */
.elevation-4 {
box-shadow:
0 14px 28px rgba(0, 0, 0, 0.18),
0 5px 10px rgba(0, 0, 0, 0.10);
}
/* Elevation 5 - Modals, popovers */
.elevation-5 {
box-shadow:
0 19px 38px rgba(0, 0, 0, 0.20),
0 7px 15px rgba(0, 0, 0, 0.10);
}
Neumorphic (soft UI) effect with inset shadows
Neumorphism creates a soft, extruded look by combining a light shadow on one side with a dark shadow on the opposite side. Both the background color and the shadow colors are derived from the same base hue, creating the illusion that the element is pressed into or raised out of the surface.
.neumorphic {
background: #e0e5ec;
border-radius: 16px;
box-shadow:
8px 8px 16px #b8bec7,
-8px -8px 16px #ffffff;
}
/* Pressed / inset variant */
.neumorphic-inset {
background: #e0e5ec;
border-radius: 16px;
box-shadow:
inset 4px 4px 8px #b8bec7,
inset -4px -4px 8px #ffffff;
}
Colored glow shadow for cards
A colored glow uses a large blur radius with a saturated, semi-transparent color to create a halo effect around a card. This technique works well for featured items, call-to-action sections, or interactive card hover states.
.glow-card {
background: #fff;
border-radius: 12px;
box-shadow:
0 4px 6px rgba(0, 0, 0, 0.07),
0 0 40px rgba(99, 102, 241, 0.15);
transition: box-shadow 0.3s ease;
}
.glow-card:hover {
box-shadow:
0 8px 16px rgba(0, 0, 0, 0.10),
0 0 60px rgba(99, 102, 241, 0.30);
}
Common patterns and best practices
Shadows are one of the most important visual cues for conveying hierarchy and depth in a user interface. Following these best practices will help you create shadows that look natural and perform well.
- Use semi-transparent colors: Real shadows are never fully black. Use
rgba(0, 0, 0, 0.1)throughrgba(0, 0, 0, 0.25)for most shadows. For dark mode interfaces, use an even lower opacity or switch to a dark blue tint. - Match shadow direction to your light source: Choose a consistent light source direction (top-left is the most common convention) and apply the same offset ratios to all shadows on the page. Inconsistent shadow directions create visual confusion.
- Scale blur proportionally to offset: As the y-offset increases (the element "rises" higher), increase the blur radius proportionally. A distant shadow should be softer and more diffused than a close one.
- Use negative spread for tight shadows: A negative spread value shrinks the shadow, which is useful for creating a shadow that only appears on one side of an element or for making a soft glow that doesn't extend as wide as the blur suggests.
- Avoid animating box-shadow directly: Animating the box-shadow property triggers repaints on every frame. For animated shadows (hover states, for example), use a pseudo-element with the shadow pre-applied and animate its opacity instead.
- Define shadows as design tokens: Store your shadow scale in CSS custom properties so your entire team uses the same elevation values and they can be updated from one place.
Browser support
The box-shadow property with multiple comma-separated layers is supported in all modern browsers and has been since IE9. There are no compatibility concerns for current development. The inset keyword has the same level of support. No vendor prefixes are needed for any current browser.
For more advanced use cases, the filter: drop-shadow() function is supported in all modern browsers since 2016 and provides an alternative that follows the element's alpha channel rather than its bounding box. The upcoming CSS box-shadow Level 2 specification may introduce additional features like spread radius for individual sides, but this is not yet implemented in any browser.
FAQ
How many shadows can I layer?
There is no hard limit on the number of box-shadow layers in CSS. Browsers handle dozens of layers without visible performance impact. However, for practical design purposes, 2-4 layers usually produce the best results — one for the contact shadow, one for the ambient shadow, and optionally one or two more for highlights or inset effects.
What's the difference between blur and spread?
Blur radius controls how soft or sharp the shadow edge is — a blur of 0 produces a hard-edged shadow, while higher values create a smoother gradient. Spread radius expands or contracts the shadow before blurring: positive values make the shadow larger than the element, negative values make it smaller, which is useful for creating tight, focused shadows.
What's the difference between box-shadow and filter: drop-shadow()?
box-shadow always applies to the element's rectangular box shape, including the border-radius but ignoring any transparency within the element. filter: drop-shadow(), on the other hand, follows the element's alpha channel. This means it will outline the visible shape of transparent PNGs, SVGs, and elements with complex clip-paths. Use drop-shadow() when you need the shadow to match a non-rectangular shape, and box-shadow when you need features like inset, spread, or multiple layers (drop-shadow does not support any of these).
Do shadows affect page performance?
Shadows are painted by the browser's rendering engine and can trigger repaints when their values change. A few static shadows have negligible impact, but multiple shadows with large blur radii on many elements can add up, especially during scrolling or animation. For animated shadows, a common optimization technique is to apply the shadow to an ::after pseudo-element with its full target values, then animate only the pseudo-element's opacity. This way, the browser only composites a layer change rather than recalculating and repainting the shadow on every frame.
Can I use CSS variables inside box-shadow?
Yes, CSS custom properties work for individual shadow values like offset, blur, spread, and color. This makes it straightforward to create theme-based elevation systems where switching from light mode to dark mode only requires updating the shadow color variables. For example, you can define --shadow-color: rgba(0, 0, 0, 0.1) and reference it in your box-shadow declaration, then override it to rgba(0, 0, 0, 0.4) in a dark theme context.