Box Shadow Basics
The CSS box-shadow property accepts one or more shadow definitions, each with up to six values: horizontal offset, vertical offset, blur radius, spread radius, color, and an optional inset keyword.
/* Basic shadow */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Offset-x | Offset-y | Blur | Spread | Color */ box-shadow: 4px 8px 16px 2px rgba(0, 0, 0, 0.1); /* Inset shadow */ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.12);
The blur radius controls how soft or hard the shadow edge appears. A value of 0 creates a perfectly sharp shadow, while higher values create a more diffused, natural look. The spread radius expands or contracts the shadow size โ positive values make it larger, negative values shrink it.
Layered Shadow Technique
The secret to realistic shadows is layering multiple shadows on a single element. Real-world light produces shadows with both a soft ambient component and a sharper, directional component. Combining these in CSS creates dramatically more natural results than a single shadow.
/* Realistic layered shadow */
.card {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04), /* tight, subtle */
0 4px 8px rgba(0, 0, 0, 0.06), /* mid-range */
0 16px 32px rgba(0, 0, 0, 0.08); /* large, ambient */
}
Each layer serves a purpose. The first (smallest) shadow defines the element's edge against its background. The middle shadow provides the primary sense of elevation. The largest shadow adds ambient depth that ties the element into the overall lighting of the page.
When layering shadows, keep the opacity low on each individual layer. The combined effect will look natural without becoming too dark or heavy. A common mistake is making each layer too opaque.
Building an Elevation System
Material Design popularized the concept of elevation โ using shadow intensity to communicate hierarchy. You can build your own system with CSS custom properties:
:root {
--shadow-xs: 0 1px 2px rgba(0,0,0,0.05);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06),
0 2px 6px rgba(0,0,0,0.04);
--shadow-md: 0 2px 4px rgba(0,0,0,0.04),
0 6px 16px rgba(0,0,0,0.06);
--shadow-lg: 0 4px 8px rgba(0,0,0,0.04),
0 12px 32px rgba(0,0,0,0.08);
--shadow-xl: 0 8px 16px rgba(0,0,0,0.06),
0 24px 48px rgba(0,0,0,0.1);
}
.card { box-shadow: var(--shadow-sm); }
.card:hover { box-shadow: var(--shadow-lg); }
.modal { box-shadow: var(--shadow-xl); }
.dropdown { box-shadow: var(--shadow-md); }
This approach keeps your shadows consistent across the entire project. When you need to adjust the lighting feel, you change the custom properties once and every component updates automatically.
Colored & Brand Shadows
Using colored shadows instead of black/gray ones creates a softer, more modern aesthetic. The trick is to use a darker, more saturated version of the element's background color as the shadow color:
/* Purple button with matching shadow */
.btn-purple {
background: #7c6fff;
box-shadow: 0 4px 14px rgba(124, 111, 255, 0.4);
}
/* Colored card shadow */
.card-warm {
background: #fff;
box-shadow: 0 8px 30px rgba(255, 111, 176, 0.15);
}
Inset Shadows for Depth
The inset keyword places the shadow inside the element, creating an impression that the element is pressed into the surface. This is commonly used for input fields, toggles, and pressed button states:
/* Recessed input field */
.input {
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.08);
border: 1px solid #e2e8f0;
}
/* Pressed button state */
.btn:active {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
transform: translateY(1px);
}
Performance Tips
Box shadows are rendered by the browser's paint step. While generally fast, there are some things to watch:
- Animating box-shadow directly triggers repaint on every frame. Instead, use a pseudo-element with the target shadow and animate its
opacityโ this is compositor-friendly and much smoother. - Very large blur values (100px+) on many elements can impact scroll performance. Use sparingly on frequently-scrolled content.
- Combine with
will-change: transformif you're animating a shadow transition on hover โ this promotes the element to its own compositing layer.
/* Performant shadow animation with pseudo-element */
.card {
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.card::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 12px 40px rgba(0,0,0,0.15);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.card:hover::after {
opacity: 1;
}
Build Shadows Visually
Skip writing shadow CSS by hand. Use our free Box Shadow Generator to layer shadows and copy the code.
Open Box Shadow Generator โ