The CSS box-shadow property is one of the most versatile tools in a web designer's toolkit. It creates depth, draws attention to interactive elements, and when used creatively can produce effects that once required image editors. Yet many developers stick to a single generic shadow and never explore the property's full potential.
This guide covers everything from basic syntax to advanced multi-layer techniques, modern design trends like neumorphism and glassmorphism, performance considerations, and accessibility best practices. By the end, you'll have the knowledge to craft shadows that feel natural, purposeful, and performant.
Understanding the Box-Shadow Syntax
The box-shadow property accepts one or more shadow layers, each defined by up to six values:
box-shadow: [inset] offset-x offset-y blur-radius spread-radius color;
- inset (optional): Places the shadow inside the element rather than outside
- offset-x: Horizontal displacement — positive moves right, negative moves left
- offset-y: Vertical displacement — positive moves down, negative moves up
- blur-radius (optional, default 0): The larger the value, the softer and more spread the shadow edge
- spread-radius (optional, default 0): Positive expands the shadow, negative shrinks it
- color: Any CSS color value — rgba and hsla allow transparency control
Multiple shadows are separated by commas and rendered back-to-front (first shadow on top).
Multi-Layer Shadows for Realism
Real-world objects don't cast a single uniform shadow. Light sources produce complex shadow patterns with varying softness and intensity depending on distance and angle. Multi-layer shadows in CSS simulate this natural behavior.
The Two-Layer Technique
The simplest realistic shadow combines a tight, dark shadow for definition with a larger, softer shadow for ambient depth:
/* Tight key shadow + soft ambient shadow */
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 4px 12px rgba(0, 0, 0, 0.08);
The first layer grounds the element; the second creates the perception of floating above the surface.
Layered Depth (Smooth Shadows)
For ultra-smooth shadows that mimic real-world lighting, stack 4–5 layers with progressively larger blur and offset values while decreasing opacity:
box-shadow:
0 0.5px 1px rgba(0,0,0,0.05),
0 2px 4px rgba(0,0,0,0.05),
0 4px 8px rgba(0,0,0,0.05),
0 8px 16px rgba(0,0,0,0.04),
0 16px 32px rgba(0,0,0,0.03);
This produces a shadow that feels organic and lifelike — far superior to a single heavy shadow with high blur.
Inset Shadows
Adding the inset keyword flips the shadow inside the element, creating effects that simulate pressed buttons, recessed panels, or inner glows:
/* Pressed button effect */
.btn-pressed {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Inner glow */
.inner-glow {
box-shadow: inset 0 0 20px rgba(59, 130, 246, 0.3);
}
Inset shadows are essential for creating depth that goes "into" the surface — a key ingredient in neumorphic design.
Material Design Elevation
Google's Material Design system uses shadow to communicate hierarchy and interaction. Elements at higher elevations cast larger, softer shadows — mimicking objects held closer to a light source and farther from a surface.
| Elevation Level | Use Case | CSS box-shadow |
|---|---|---|
| 0dp | Flat surface | none |
| 1dp | Card at rest | 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) |
| 2dp | Raised button | 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) |
| 8dp | Floating action button | 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23) |
| 16dp | Modal/dialog | 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22) |
| 24dp | Navigation drawer | 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22) |
The key principle: interactive elements should visually "lift" on hover/focus by increasing their elevation shadow. This gives users clear feedback about what is clickable.
Neumorphism (Soft UI)
Neumorphism creates a soft, extruded appearance by combining a light shadow and a dark shadow on a background that matches the element color:
.neumorph-card {
background: #e0e5ec;
border-radius: 16px;
box-shadow:
8px 8px 16px #b8bec7,
-8px -8px 16px #ffffff;
}
The light shadow simulates light hitting the top-left; the dark shadow simulates the bottom-right being in shade. The background must be close to the shadow colors for the effect to work.
Glassmorphism
Glassmorphism combines frosted-glass blur with subtle shadows to create translucent card effects. While backdrop-filter handles the blur, box-shadow adds the crucial edge definition and depth:
.glass-card {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.1),
inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
The outer shadow creates lift while the inset shadow adds a subtle inner highlight that reinforces the glass edge.
Color Shadows
Traditional shadows use black or gray at low opacity. Color shadows — using the element's own hue — create a modern, vibrant glow effect:
.btn-primary {
background: #3b82f6;
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4);
}
.btn-danger {
background: #ef4444;
box-shadow: 0 4px 14px rgba(239, 68, 68, 0.4);
}
Color shadows make buttons and cards feel luminous — as if they emit light rather than simply block it. They're particularly effective on dark backgrounds and in modern SaaS interfaces.
Animated Shadows and Hover Effects
Shadows can transition on hover to provide feedback, but animating box-shadow directly is expensive because it triggers repaint on every frame. The performant approach uses a pseudo-element:
.card {
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
}
.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.2s ease;
z-index: -1;
}
.card:hover {
transform: translateY(-2px);
}
.card:hover::after {
opacity: 1;
}
This technique pre-renders both shadow states and animates only opacity and transform — both GPU-accelerated properties that run at 60fps without triggering layout or paint.
Performance Considerations
Shadows are purely cosmetic and don't affect layout, but they do impact rendering performance:
- Blur radius cost: Larger blur values require more computation. A 50px blur is significantly more expensive than a 5px blur.
- Number of shadows: Each layer adds rendering work. Keep production shadows under 5 layers per element.
- Animating shadows: Never transition
box-shadowdirectly on many elements simultaneously. Use the opacity/pseudo-element pattern instead. - Mobile devices: Budget GPUs struggle with large shadows on scrolling content. Test on real low-end devices.
- will-change: Adding
will-change: transformto shadowed elements can promote them to their own compositor layer, improving scroll performance.
Text-Shadow vs Box-Shadow
While related, these properties serve different purposes:
| Feature | box-shadow | text-shadow |
|---|---|---|
| Applies to | Element's border box | Text content only |
| Inset support | Yes | No |
| Spread radius | Yes | No |
| Respects border-radius | Yes | N/A |
| Common use | Cards, buttons, modals | Text glow, emboss, legibility on images |
Accessibility Best Practices
Shadows enhance visual design but must never be the sole indicator of state or interactivity:
- Don't rely on shadow alone for focus states: Always combine shadow with an outline or border change that meets 3:1 contrast. Screen readers can't perceive shadows.
- Hover shadow changes need alternatives: Touch devices have no hover. Ensure your UI works without hover-shadow feedback.
- Respect reduced-motion preferences: Wrap shadow animations in
@media (prefers-reduced-motion: no-preference)— users who are sensitive to motion should get instant state changes. - Neumorphism caution: If you use soft-UI shadows as the only boundary between elements, add subtle borders or contrast differences for users with low vision.