Decide what the shadow should explain
A shadow can say that a surface floats above another, that a control is inset, or that an item is being moved. If every card has a strong shadow, that signal stops distinguishing anything.
Start with a small task: separate a white card from a pale background. A subtle border may be enough. If the card needs depth, add a close contact layer and a broader ambient layer.
.card {
border: 1px solid rgb(20 32 56 / 0.08);
box-shadow:
0 2px 4px rgb(20 32 56 / 0.08),
0 14px 32px rgb(20 32 56 / 0.1);
}
Separate softness from size
Blur changes softness at the edge. Spread expands or contracts the silhouette before blur. A large blur with a small negative spread can create a soft shadow tucked below a card instead of a halo around every side.
Vertical offset moves the apparent light direction. A downward offset suggests light from above. Keep that direction consistent across related components.
Color is part of the surface
Pure black is not mandatory. A low-opacity navy shadow on a cool interface can feel integrated. Opacity is usually the first control to reduce when a shadow feels dirty. Increasing blur alone spreads the same dark impression over a larger area.
The first layer is painted above later layers. Put the sharper contact shadow first. Use an inset layer only when the surface should appear recessed.
Check the edge cases
A shadow does not reserve layout space. It can overlap neighboring content or be clipped by an ancestor's overflow: hidden. Neither means the CSS is invalid. Test the component in its actual setting, including dark mode and narrow layouts.
A text shadow follows glyph shapes and has no spread or inset controls. It cannot rescue an unreadable foreground/background pair.
Try one change
Build two layers below. Give the first a 2px vertical offset and 4px blur, and the second a 14px offset and 32px blur. Toggle each layer's opacity between zero and a small value. Notice which part of the depth impression disappears.