The short version
- The CSS box shadow property takes an optional inset keyword, two offsets, a blur radius, a spread radius and a colour.
- A single box shadow looks artificial because real objects cast a sharp contact shadow and a wide soft one at the same time.
- Layering three to five shadows, each with roughly double the offset and blur of the last and a low opacity, produces a soft, believable shadow.
- Tinting the shadow towards the background's hue looks more natural than pure black on coloured or warm surfaces.
- Animating box-shadow repaints on every frame, so fade the opacity of a pseudo-element that carries the larger shadow instead.
On this page
A CSS box shadow is written box-shadow: 0 4px 12px rgb(0 0 0 / 0.15): a horizontal offset, a vertical offset, a blur radius, an optional spread and a colour. That single shadow is fine for a quick prototype, but it rarely looks real. The shadows that convince the eye are layered: several shadows on one element, each larger and fainter than the last, tinted to suit the surface beneath.
box-shadow syntax
/* inset? | offset-x | offset-y | blur | spread | color */
.card { box-shadow: 0 4px 12px 0 rgb(0 0 0 / 0.15); }
/* Negative spread tucks the shadow under the box */
.card { box-shadow: 0 12px 24px -8px rgb(0 0 0 / 0.25); }
/* Inset: a shadow inside the box, like a pressed well */
.well { box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.12); }
/* Spread only: a hairline that follows border-radius */
.chip { box-shadow: 0 0 0 1px rgb(15 23 42 / 0.08); }
/* Several shadows, comma separated, first on top */
.card { box-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 8px 16px rgb(0 0 0 / 0.08); }border-radius, which is why a spread only shadow makes a neat rounded hairline without changing layout.insetOffEffect
Default
Effect
Effect
Effect
currentcolorEffect
Why a single shadow looks fake
Put a book on a table and look at its shadow. Right at the edge where it touches, the shadow is dark and crisp. Further out it becomes wide and faint, because light wraps around the object from many directions. One box-shadow can only be one of those things at a time: tight and hard, or wide and blurry. Either way the eye notices something is missing.
Single shadow
- One medium blur in 25 percent black
- Grey halo all round the card
- Hard to tell how high the card floats
- Looks dirty on coloured backgrounds
Layered shadow
- Four shadows, each fainter and wider
- Dense near the edge, airy far away
- Height reads at a glance
- Tinted to sit naturally on its surface
How to layer box shadows
The method is simple: start small and sharp, then double the offset and blur for each extra layer, keeping every layer faint. The opacities add up where the layers overlap, so individual values can be surprisingly low.
- 1
Start with a contact shadow
0 1px 1pxat around 8 percent opacity. This is the dark line right under the edge. - 2
Double for each layer
Then
0 2px 2px,0 4px 4px,0 8px 8px. Offset and blur grow together, so light seems to come from above. - 3
Keep each layer faint
Between 5 and 10 percent each. If the result looks heavy, remove the largest layer before turning anything darker.
- 4
Tint the colour
Replace black with a deep version of the background hue, such as a navy on blue pages or a brown on cream.
:root {
/* Shadow colour as channels, so opacity can vary per layer */
--shadow-color: 222 47% 11%;
--shadow-sm:
0 1px 1px hsl(var(--shadow-color) / 0.08),
0 2px 2px hsl(var(--shadow-color) / 0.06);
--shadow-md:
0 1px 1px hsl(var(--shadow-color) / 0.08),
0 2px 2px hsl(var(--shadow-color) / 0.07),
0 4px 4px hsl(var(--shadow-color) / 0.06),
0 8px 8px hsl(var(--shadow-color) / 0.05);
--shadow-lg:
0 1px 1px hsl(var(--shadow-color) / 0.07),
0 2px 2px hsl(var(--shadow-color) / 0.07),
0 4px 4px hsl(var(--shadow-color) / 0.06),
0 8px 8px hsl(var(--shadow-color) / 0.06),
0 16px 16px hsl(var(--shadow-color) / 0.05),
0 32px 32px hsl(var(--shadow-color) / 0.04);
}
.card { box-shadow: var(--shadow-md); }
.dialog { box-shadow: var(--shadow-lg); }--shadow-color once and every shadow in the system follows.Storing shadows as variables turns them into design tokens, which keeps every card, menu and dialog consistent. CSS variables for theming and design tokens show how to wire them into a full theme.
An elevation scale for an interface
Shadow is a signal of height, so it should mean the same thing everywhere. Pick three or four levels and assign them to jobs. Anything that floats higher gets a bigger, softer shadow, never just a darker one.
Use it for
--shadow-smUse it for
--shadow-mdUse it for
--shadow-lgUse it for
Shadows on coloured and dark backgrounds
Pure black shadows turn grey and muddy on a coloured surface, the same way black mixed into paint dulls it. Take the shadow colour from the background instead: a deep teal shadow on a mint page, a dark plum on a lilac one. On gradient backgrounds, pick the darkest stop of the gradient and use it at low opacity.
In dark mode, shadows almost disappear, because there is little light for them to block. Show height by making raised surfaces slightly lighter instead, and keep a faint hairline at the edge. Dark mode design goes through the surface colours. For panels that float over imagery, a backdrop filter often separates layers better than a heavier shadow.
Animating box-shadow without jank
Changing box-shadow on hover forces the browser to repaint the shadow on every frame of the transition. On one button that is fine. On a grid of fifty cards it stutters. The standard fix is to put the larger shadow on a pseudo-element and fade its opacity, which the browser can composite cheaply.
.card {
position: relative;
box-shadow: var(--shadow-sm);
}
.card::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: var(--shadow-lg);
opacity: 0;
transition: opacity 200ms ease-out;
pointer-events: none;
}
.card:hover::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
.card::after { transition: none; }
}More on what costs paint time is in CSS gradient performance, and the same layering approach suits buttons in gradient buttons in CSS.
When to skip the shadow
Shadows separate a surface from what is behind it. If the background is a rich gradient or photograph, a strong shadow fights it. Often a quieter background does more for the card than any shadow could. Gradiently's Marks are built for that: each one is a living background with its own colours, light and grain, and the words you place over it stay readable because the Mark calms itself behind them, with no boxes or shadows doing the work.
Questions people ask
What are the values in CSS box-shadow?
An optional inset, a horizontal offset, a vertical offset, a blur radius, a spread radius and a colour, for example 0 4px 12px 0 rgb(0 0 0 / 0.15).
How do I make a soft box shadow in CSS?
Layer three to five shadows, doubling the offset and blur each time, at 5 to 10 percent opacity each. Tint them towards the background colour.
How do I add more than one box shadow?
Separate them with commas in one box-shadow declaration. The first shadow listed is drawn on top.
What does spread do in box-shadow?
It grows or shrinks the shadow before it is blurred. A negative spread keeps a large blur from showing at the sides of the box.
Is animating box-shadow bad for performance?
It repaints every frame. Put the hover shadow on a pseudo-element and transition its opacity instead.
Written by Gradiently
The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.
See our profile