DEV Community

Snappy Tools
Snappy Tools

Posted on • Originally published at snappytools.app

CSS Box Shadows: The Complete Guide From Flat to Floating

The box-shadow property is one of the most powerful CSS properties for making UI elements feel real. A well-placed shadow turns a flat button into something you want to click. A subtle card shadow separates content from the background without a hard border. But getting it right from memory is genuinely hard — the syntax has five parameters, and composing multiple shadows mentally is nearly impossible.

This guide covers everything you need to know about CSS box shadows, from the syntax to layered shadows, common presets, and performance tips.

The Syntax

box-shadow: [inset] offset-x offset-y [blur-radius] [spread-radius] color;
Enter fullscreen mode Exit fullscreen mode

Breaking down each part:

  • offset-x — how far the shadow is pushed right (negative = left)
  • offset-y — how far the shadow is pushed down (negative = up)
  • blur-radius — how soft the shadow edge is (0 = sharp, higher = softer)
  • spread-radius — how much bigger the shadow is than the element (negative = smaller)
  • color — use rgba() for transparency control
  • inset — optional keyword that puts the shadow inside the element

Five Presets Worth Memorising

These cover 90% of UI use cases.

Soft — subtle depth for cards:

box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
Enter fullscreen mode Exit fullscreen mode

Raised — button that looks clickable:

box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08);
Enter fullscreen mode Exit fullscreen mode

Floating — modal or dropdown:

box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18), 0 4px 12px rgba(0, 0, 0, 0.1);
Enter fullscreen mode Exit fullscreen mode

Deep — primary CTA button:

box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);
Enter fullscreen mode Exit fullscreen mode

Inset — pressed state or sunken input:

box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
Enter fullscreen mode Exit fullscreen mode

Layered Shadows

You can stack multiple shadows by separating them with commas. This is how you get the natural-looking light falloff that real objects have — a sharp inner shadow for the contact shadow and a diffused outer shadow for the ambient light:

box-shadow:
  0 1px 2px rgba(0, 0, 0, 0.20),
  0 4px 8px rgba(0, 0, 0, 0.12),
  0 16px 32px rgba(0, 0, 0, 0.06);
Enter fullscreen mode Exit fullscreen mode

The first shadow is the sharpest and darkest (close contact). Each subsequent shadow is softer and more transparent (further from the surface). This technique is directly inspired by how actual light works.

Colour-Tinted Shadows

Pure black shadows (rgba(0,0,0,...)) can look synthetic. For a more organic result, tint the shadow toward the element's colour or your page's dominant hue:

/* Green-tinted shadow for a success button */
.btn-success {
  background: #2f855a;
  box-shadow: 0 4px 12px rgba(47, 133, 90, 0.4);
}
Enter fullscreen mode Exit fullscreen mode

This creates a glow effect that feels cohesive rather than pasted on.

Spread Radius: Inset Borders and Glow Effects

The spread radius is underused. At positive values, it creates a glow or ring effect:

/* Focus ring that doesn't shift layout */
:focus-visible {
  box-shadow: 0 0 0 3px rgba(47, 133, 90, 0.5);
}
Enter fullscreen mode Exit fullscreen mode

This is far better than outline for styling — it follows border-radius, and outline-offset is tricky to control. Using box-shadow for focus rings is a design system staple.

At negative values, the spread radius makes the shadow smaller than the element — useful for creating the illusion that a shadow only falls below the bottom edge:

box-shadow: 0 4px 6px -2px rgba(0, 0, 0, 0.2);
Enter fullscreen mode Exit fullscreen mode

Performance: When to Use will-change

Animating box-shadow is expensive — the browser repaints on every frame. If you animate shadows on hover or during scroll, move the shadow to a ::after pseudo-element and animate opacity instead:

.card {
  position: relative;
}

.card::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.card:hover::after {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

opacity composites on the GPU — no repaint. This gives you silky 60fps shadow transitions even on low-end devices.

Dark Mode Shadows

In dark mode, dark shadows on a dark background are invisible. Switch to subtle light or coloured shadows instead:

@media (prefers-color-scheme: dark) {
  .card {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
    /* Or use a border instead */
    border: 1px solid rgba(255, 255, 255, 0.08);
  }
}
Enter fullscreen mode Exit fullscreen mode

The common dark mode pattern is to replace heavy shadows with a combination of slightly lighter backgrounds and hairline borders — shadows in dark themes often do more harm than good.

Tools Worth Bookmarking

Generating complex layered shadows by hand is tedious. If you're working with CSS design tools, a few free browser-based tools can save you significant time:

  • CSS Gradient Generator — create linear, radial, and conic gradients with a visual editor and copy the CSS instantly
  • Color Picker — pick any colour and copy it in hex, RGB, or HSL — useful when choosing shadow tint colours
  • Color Contrast Checker — verify WCAG AA/AAA compliance between your shadow-affected text and background

These are all free, run in the browser with no data uploaded, and no signup required.

The One Rule

The most common mistake with box shadows: too many, too dark, too spread. If you can see your shadow clearly from three feet away, it's too strong. Shadows should create depth, not compete for attention. When in doubt, halve the opacity and see if the depth is still there. Usually it is.

Happy building.

Top comments (0)