A box-shadow is a copy of the element's box, painted behind it: moved by two offsets, softened by a blur, grown or shrunk by a spread, and filled with one flat colour. That is the entire model. box-shadow: 0 8px 24px -6px rgba(16, 18, 42, 0.16); reads as no horizontal offset, 8 px down, a 24 px soft edge, a shape pulled in 6 px on every side, in a dark indigo at 16% opacity. The shadow takes up no space in the layout — nothing on the page moves to make room for it.
What each value does, in order
The lengths are read by position, so you cannot shuffle them. The colour is the exception: it can sit at either end, so rgba(0, 0, 0, 0.2) 0 4px 8px is as valid as the same list with the colour last.
- Offset X — positive moves the shadow right, negative moves it left.
- Offset Y — positive moves it down, negative up. This is the value that decides where your imaginary light is.
- Blur — how wide the soft edge is. Optional, defaults to 0, and cannot be negative.
- Spread — how much bigger or smaller the shadow shape is than the box. Optional, defaults to 0, and can be negative. Because the lengths are positional, you cannot write a spread without writing a blur first, even if that blur is 0.
- Colour — optional. Leave it out and the browser uses the element's own text colour, which is almost never what you meant.
The lengths accept any CSS length unit — px, rem, em — but not percentages. A percentage makes the whole declaration invalid and the browser discards it, which is one of the quieter reasons a shadow "does nothing".
Blur is not how far the shadow travels
This is the value nearly everyone misreads. The blur radius does not push the shadow further out. It describes how wide the fade from solid to transparent is, and that fade is centred on the edge the shadow would have had. Set 24 px of blur and the transition spans roughly 24 px: about half of it outside the shape, about half eaten inward. The spec defines the result as approximating a Gaussian blur with a standard deviation of half the blur radius.
Two things follow from that. A large blur with no spread leaks out of the sides and the top, which is why a shadow meant to sit under a card ends up ringing it like a halo. And a shadow with blur but no offset is visible on all four sides, because half the fade escapes in every direction. That is a glow, not a shadow. Useful for a focus ring, useless for depth.
What spread is actually for
Spread resizes the shadow rectangle, and only then is the result blurred. Order matters here: inflate first, soften second.
Negative spread is the fix for the leaking above. 0 18px 36px -14px shrinks the shape by 14 px before blurring, so almost nothing escapes sideways and what survives reads as a pool of shade underneath the element rather than a fog around it. Most shadows that look right in a real interface have a negative spread on their largest layer.
Spread is also how you get a shadow on one side only. Make it negative and close to the blur value, then offset in the direction you want: 0 10px 8px -8px puts shade under an element and nowhere else. And on an inset layer the direction flips — positive spread makes the inner shadow thicker, because the shape is being grown inward.
Why one shadow always looks fake
Real light does two things at once. Where an object meets a surface there is a small, dark, sharp contact shadow. Further out there is a much larger, much fainter, diffuse one. A single CSS shadow can be tight or soft, never both, so it reads as a sticker laid on the page instead of an object sitting above it.
The fix is to stack layers. Comma-separate them inside one declaration, first in the list painted on top: box-shadow: 0 1px 2px rgba(16, 18, 42, 0.08), 0 8px 24px -12px rgba(16, 18, 42, 0.16);. The small tight layer does the contact, the large soft one does the ambient light, and each is faint enough on its own that the sum stays subtle.
Tuning that by hand means editing six numbers and reloading between each guess, which is where a shadow builder with a live preview pays for itself: it keeps up to four layers at once, lets you edit each separately, and prints the finished declaration. Two layers is usually enough. Two caveats on it: the preview is one box on a flat backdrop rather than your page, and it does not write text-shadow, which takes two or three lengths and a colour — no spread, no inset — so the negative-spread trick above does not carry over to text.
Why your shadow looks grey and dirty
Because it is pure black. Compositing black over a colour scales every channel toward zero. The hue survives, but the colourfulness scales down with the lightness, so the shaded strip on a warm background goes flat and grey next to the surface around it. Real shade does not do that: it gets filled in by whatever light is bouncing off everything else, so it keeps a tint. Use a very dark version of the surface's own hue instead — a deep navy under a cool grey page, a dark brown under cream. The geometry does not change at all, and the result stops reading as grime.
Shadow colours are almost always written as rgba(), so you need the channel values of that dark tint rather than the hex you designed with. A hex to RGB converter gets you there in one step, and it also generates the shades if you need to push a brand colour darker first. If the notation itself is the sticking point, what hex, RGB and HSL each describe is the background reading.
Where inset shadows are painted
Add inset to a layer and it is drawn inside the padding box instead: on top of the background, underneath the content, and clipped by the corner radius so it follows rounded corners without you doing anything. That is what makes an input look recessed or a button look pressed while it is held down.
Inset and outset layers can live in the same comma-separated list. They are independent and painted in order, first on top. Outset layers follow the border box and inset layers the padding box, and both are clipped to the corner radius, so the radius and the shadow shape are tied together — change one and the other reshapes with it, which is worth knowing before you fight either. The way border-radius really behaves covers the half that surprises people.
box-shadow or filter: drop-shadow?
box-shadow follows the border box, always. If your element is a transparent PNG, an SVG icon or a shape cut out with clip-path, you still get a rectangle floating behind it. filter: drop-shadow() follows the visible alpha channel and gives you the outline you actually wanted.
The trade: drop-shadow costs more to render, it takes no spread value at all — so the negative-spread trick above is unavailable — and it applies to the element and everything inside it, children included. Use box-shadow for rectangles and rounded rectangles, which is most of an interface, and drop-shadow when the silhouette matters.
What breaks a shadow that looked fine
- overflow: hidden clips it. A shadow occupies no layout space, so an ancestor that hides overflow cuts it off like anything else sticking out. Add padding to the parent, or move the shadow to an element outside the clipped area.
- A second box-shadow rule replaces the first. It is one property, not a list you append to. If a later rule sets
box-shadow, your earlier layers are gone — put every layer in the same declaration. - Animating blur or spread repaints every frame. That gets expensive fast on large elements. Put the shadow on a pseudo-element and animate that element's opacity instead; opacity is cheap, re-blurring is not.
- Dark interfaces barely show it. Black at 14% over a near-black surface is close to invisible. Dark themes usually separate surfaces with a faint light border, or by making the raised surface lighter, rather than reusing the light-mode shadow values.
Last thing: test on the background you are actually shipping. A shadow tuned on white can vanish over a photograph or a gradient, and that is the most common reason a card looks convincing in the design file and flat on the live page.
The layered version is genuinely fiddly to write by hand, so the box shadow generator here keeps four layers alive at once and updates the CSS as you drag — switch its preview background to dark before you decide you are finished, because that is where most shadows fall apart. If what you are still fighting is the colour rather than the geometry, building a colour palette that holds together explains how to derive the dark tint your shadow should have been using from the start.
Frequently asked questions
How does box-shadow work in CSS?
The browser draws a copy of the element’s box behind it, shifted by the horizontal and vertical offsets, resized by the spread, softened by the blur and filled with a single flat colour. The shape follows the border box, including any border-radius. It is painted, not laid out, so it never moves other elements.
What do the four numbers in box-shadow mean?
In order: horizontal offset, vertical offset, blur radius, spread radius. The last two are optional and default to zero, and the colour can be written before or after the lengths. Percentages are not allowed for any of them.
How do I make a shadow on only one side?
Give the shadow a negative spread close to its blur value, then offset it toward the side you want. Something like 0 10px 8px -8px keeps the fade from escaping left, right and upward, so only the bottom edge shows shade.
Why is my box-shadow not showing up?
The usual causes are an ancestor with overflow: hidden clipping it, a later CSS rule overwriting the whole box-shadow property, or an opacity so low it disappears against the background. A percentage in one of the lengths also invalidates the entire declaration.
Can you animate a box-shadow?
Yes, box-shadow is animatable, but changing blur or spread forces the browser to repaint the shadow on every frame and that is slow on large elements. The usual workaround is to put the shadow on a ::after pseudo-element and transition that element’s opacity between two states.
Last updated September 19, 2026