How to Build a Perfect CSS Box Shadow in 5 Minutes
A good CSS box shadow is subtle, layered, and matches the lighting of the rest of the design. Learn the four-part anatomy of a real shadow (offset, blur, spread, color), the trick of stacking two shadows for depth, and the values that work in production.
A perfect CSS box shadow is two stacked shadows: a tight, dark one close to the element (the contact shadow) and a soft, lighter one further away (the ambient shadow). The four parts of every box-shadow: offset-x, offset-y, blur, spread, and color. For the contact shadow, use small offset (0-2px) and small blur (2-4px) with low opacity black. For the ambient shadow, use larger offset (4-12px) and larger blur (8-24px) with even lower opacity. The <a href="/css-gradient-generator">Uttir CSS Gradient Generator</a> shows the same shadow-stacking principle for gradients, and the <a href="/color-picker">Color Picker</a> is the right tool to dial in the exact shadow color with alpha.
Most CSS box shadows look bad. They are too dark, too sharp, too uniform, or too obviously a "drop shadow" that the design never asked for. The reason is that a single box-shadow value, with the default blur and zero spread, does not look like real-world lighting. Real shadows have multiple parts: a tight, dark contact shadow where the object meets the surface, and a soft, lighter ambient shadow that fades off into the background.
This post covers the anatomy of a good box shadow, the trick of stacking two shadows for depth, the values that work in production, and how to tune them. The Uttir CSS Gradient Generator applies the same layered approach to gradients; the Color Picker is the right tool for dialing in the exact shadow color with alpha.
The four parts of every box-shadow
Every CSS box-shadow has four parts, in this order: offset-x, offset-y, blur-radius, spread-radius, and color. The shorthand is box-shadow: X Y Blur Spread Color. The first two are required; the rest have defaults (blur defaults to 0, spread defaults to 0, color defaults to the current text color).
Offset-x and offset-y. How far the shadow is shifted from the element. Positive values shift right and down; negative values shift left and up. A shadow with offset (0, 0) is centered on the element; a shadow with offset (0, 4) is shifted 4 pixels down. For a top-down light source (the default in most UI design), use a small positive Y offset (2-8 pixels).
Blur-radius. How soft the shadow is. A value of 0 produces a hard, sharp shadow (rarely what you want). Larger values produce softer shadows. For UI design, blur is usually 4-24 pixels.
Spread-radius. How much larger than the element the shadow is. A value of 0 means the shadow is the same size as the element. Positive values make it larger (and softer-looking); negative values make it smaller (and tighter). For most UI shadows, spread is 0 or slightly negative.
Color. The shadow color. Almost always a black or near-black with low alpha (10-25%). A fully opaque black shadow looks like a hard cutout; a black shadow with 20% alpha looks like a real shadow. The Hex to RGB tool is the fastest way to convert a design-system color into the rgba() format box-shadow needs.
Why one shadow is not enough
A single box-shadow can represent one part of a real shadow. Real shadows are not single shapes; they are layered. A hand resting on a desk casts a sharp shadow right under it (where the hand touches the desk) and a soft, larger shadow that fades off into the room. The two parts have different characteristics: the contact shadow is tight and dark; the ambient shadow is soft and faint.
To replicate this in CSS, you stack two box-shadows on the same element. The first one (in the CSS) is the contact shadow: small offset, small blur, darker. The second one is the ambient shadow: larger offset, larger blur, lighter. The browser composites them into a single, more believable shadow.
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.06),
0 4px 12px rgba(0, 0, 0, 0.10);
This is the standard "subtle UI shadow" pattern. The first shadow is the tight contact shadow (1 pixel offset, 2 pixel blur, 6% black). The second is the ambient shadow (4 pixel offset, 12 pixel blur, 10% black). Both are subtle; both are visible; together they look like a real shadow.
Compare with the single-shadow version:
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
This is darker, harder, and less believable. It works in a pinch, but it does not have the depth of the two-shadow version. The two-shadow version is the difference between "this design has depth" and "this design has a drop shadow."
The standard values
Four pairs of values cover almost every shadow in a typical UI design. Memorise these and you can produce a good shadow without thinking.
Subtle. For raised elements that should look like they are slightly above the surface (cards on a page, list items):
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.05),
0 2px 6px rgba(0, 0, 0, 0.08);
Medium. For floating elements that need to feel distinct from the background (modals, popovers, dropdowns):
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.06),
0 8px 16px rgba(0, 0, 0, 0.12);
Heavy. For elements that are clearly hovering above the page (tooltips, menus on hover):
box-shadow:
0 4px 8px rgba(0, 0, 0, 0.08),
0 16px 32px rgba(0, 0, 0, 0.16);
Inset. For pressed elements (input fields, pressed buttons):
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.08);
The Uttir CSS Gradient Generator follows the same two-layer pattern for gradients (one tight stop, one soft stop) and produces output that drops straight into a stylesheet.
The lighting direction
The shadow direction has to match the rest of the design's lighting. If your design has a top-left light source, the shadow falls to the bottom-right, with positive X and Y offset. If your design has a top-right light source, the shadow falls to the bottom-left, with negative X offset.
Most UIs use a top-down or top-left light source by default. The values above are for top-down (offset 0 in X, positive in Y). For top-left, use offset (2, 4) instead of (0, 4).
Mixing shadow directions in the same design is a visual bug. If one card has its shadow falling right and another has its shadow falling left, the design looks broken even if the shadows are technically correct. Pick a light direction and apply it consistently across every shadow in the design.
Color considerations
The shadow color is almost always a desaturated dark color with low alpha. Pure black (#000) at 20% alpha is the most common choice; a slightly warm dark (rgba(20, 20, 30, 0.15)) is the second most common. A colored shadow (a blue or red tint) is a stylistic choice that can work in some designs but reads as "unusual" in most.
The Uttir Color Picker is the right tool to dial in the exact shadow color. Pick the hue, adjust the alpha, and copy the rgba() value. The Hex to RGB tool is faster if you already have the hex and just need the rgba() form.
One subtlety: the shadow color should be derived from the background, not the element. A pure white element on a light grey background needs a darker shadow than a white element on a dark background, because the contrast is different. Tune the shadow for the context, not in isolation.
Performance
Box shadows are cheap to render in modern browsers, but not free. A page with hundreds of elements all casting box shadows will see a small performance hit, especially on lower-end devices. The blur radius is the most expensive part: a 32-pixel blur on a large element is more expensive than a 4-pixel blur.
A few practical limits:
- Avoid box shadows on elements that animate frequently (a shadow that re-renders 60 times per second is expensive).
- Keep blur radius under 32 pixels for most use cases; larger blurs rarely look better and always cost more.
- Use
will-change: box-shadowsparingly, only on elements where the shadow actually animates. The hint reserves a separate compositor layer for the element, which has its own cost.
For most designs, the performance is a non-issue. The values in this post are small enough that even a page with 50 shadowed elements renders fine. The performance limits matter when you are doing something unusual (a 3D visualisation with hundreds of cards, each with a complex shadow).
The CSS Flexbox Playground and CSS Grid Playground are good companions when you are iterating on a design that has shadows — the playgrounds let you see the layout, the shadow, and the spacing all at once. The CSS Minifier is the right tool once you have the final values and want to ship the smallest possible file.