A card that seems to float gently above the page, a button that gains some thickness on hover, a modal clearly detached from the content behind it: box-shadow is the property that gives depth to a flat interface, without adding a single image or extra markup.
Its syntax fits on one line, but hides a few subtleties: the exact order of its values, the inset keyword that flips everything, and the ability to stack several shadows on one element.
Definition
box-shadow casts a shadow around an element's box, following its shape, including corners rounded by border-radius. Only two values are required, the horizontal and vertical offset; blur, spread and colour stay optional.
.card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}No horizontal offset, four pixels down, a twelve-pixel blur, and black at 15% opacity: a soft, subtle shadow, the most common one on an interface card.
The five values, one by one
| Value | Role | Required? |
|---|---|---|
offset-x | Horizontal offset, positive to the right | Yes |
offset-y | Vertical offset, positive downward | Yes |
blur-radius | The blur applied to the shadow's edges | No, 0 by default |
spread-radius | Grows or shrinks the shadow's shape | No, 0 by default |
color | The shadow's colour | No, currentColor by default |
inset: the shadow moves inside
Adding the inset keyword before the numeric values flips the shadow to the inside of the box rather than the outside, an effect used to simulate a sunken form field or a pressed button.
.active-field {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}Stacking several shadows
box-shadow accepts a comma-separated list of shadows. The first one declared sits on top of the rest, which makes it possible to layer a sharp, close shadow over a wide, diffuse one, for a more realistic result than a single value could give.
This layering echoes the logic of design systems that define several elevation levels, from the most subtle to the most pronounced, each tied to a pair of values already worked out rather than improvised on every new component.
.elevated-card {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.08),
0 8px 24px rgba(0, 0, 0, 0.12);
}A box-shadow never changes the box's size in Box model terms: it pushes no neighbouring element and triggers no scrollbar. In exchange, a parent with overflow: hidden clips the shadow exactly as it would clip any other overflowing content.
box-shadow or filter: drop-shadow, which one to pick
Both properties produce a shadow, but not of the same shape. box-shadow always draws a rectangle, or the rounded shape enforced by border-radius: on an image with a transparent background, it casts the shadow of a square, not the shadow of the logo itself.
CSS filter with drop-shadow, on the other hand, follows the element's actually opaque pixels, which makes it essential on a cut-out logo or icon, where box-shadow would give a visibly wrong result.
The filter also costs more in performance over a large surface, since it reprocesses every pixel of the element on each frame, while box-shadow stays a simple layer added to the final render.
Frequently asked questions
Can box-shadow be animated with a transition?
Yes, but with care: recalculating a blurred shadow on every frame of an animation costs the browser more than a transition on transform or opacity. For a frequent hover effect, it is better to transform an already-present shadow into a stronger one rather than making it appear from nothing.
What is a negative spread value used for?
A negative value shrinks the projected shape before the blur is applied, useful for creating a shadow more concentrated than a regular border, for instance to simulate a thin focus ring around a field.
Why is my shadow cut off on one side?
Most often because a parent carries overflow: hidden or overflow: auto, which clips any shadow extending past its own bounds exactly as it would clip overflowing text or an image.