CSS Shadows: Box-Shadow, Text-Shadow, Drop-Shadow Effects

Published Updated

A shadow tells the eye how far an element floats above the page, like a sheet of paper lifted from a desk. A tight, dark edge looks close to the surface. A wider, softer edge suggests more distance. Sites once generated shadow images on the server and attached them to elements; CSS now draws the effect directly.

.card {
  box-shadow: 0 0.5rem 1rem rgb(18 28 24 / 0.2);
}
<div class="card">
  Shadow practice card
</div>

Save both examples together in one HTML file, with the CSS inside a <style> tag, and open it in your browser to see the card.

All following .card CSS examples apply to this fixture. In rgb(18 28 24 / 0.2), the value after the slash sets opacity (see the colors and units guide). This shadow sits directly below the card, blurs across one rem, and uses a partly transparent color. The card lifts without changing its size or position in the page layout.

Build a Box Shadow Step by Step

A box-shadow starts with horizontal and vertical offsets. You can then add blur, spread, color, extra layers, or the inset keyword. Reading the values in that order makes a long declaration easier to debug.

Start with Offset and Blur

.card {
  box-shadow: 0.25rem 0.5rem 0.75rem;
}

The first value moves the shadow right, the second moves it down, and the third softens the edge. Because this declaration has no color, the shadow uses currentColor, the text color the element already has; the next color step sets it explicitly. The card lifts, with a soft edge falling below and to the right. Negative offsets move the shadow above or to the left.

Add Spread

.card {
  box-shadow: 0.25rem 0.5rem 0.75rem 0.2rem;
}

The fourth length is the spread radius. A positive spread expands the shadow before blur is applied, so a wider edge becomes visible around the card. A negative spread contracts the starting shape instead. Spread changes the size of the shadow's starting shape, while blur changes the softness of its edge.

Control Color with Alpha

.card {
  box-shadow: 0.25rem 0.5rem 0.75rem 0.2rem rgb(18 28 24 / 0.18);
}

The alpha value keeps the surface underneath visible. An opaque black shadow often looks like a border or stain. With the explicit transparent color, the card has a softer edge that still separates it from the surface beneath it.

Layer Multiple Shadows

.card {
  box-shadow:
    0 1px 2px rgb(18 28 24 / 0.14),
    0 0.75rem 1.5rem rgb(18 28 24 / 0.16);
}

Commas separate the declaration into distinct shadow layers. The first narrow layer defines the card's nearby edge. The second broad layer supplies the softer sense of height, so the card shows a crisp contact edge inside a wider haze. Combining two restrained layers usually looks more natural than pushing one blur radius to an extreme.

Turn the Shadow Inward

.field {
  box-shadow: inset 0 2px 5px rgb(18 28 24 / 0.2);
}

The inset keyword draws the shadow inside the box. Instead of lifting paper from the desk, it makes the surface look pressed in. Inset shadows suit wells, recessed controls, and subtle inner edges, but they should not be the only signal that a control is interactive.

Add a Shadow to Text

The text-shadow property uses horizontal offset, vertical offset, blur radius, and color. Unlike box-shadow, text-shadow does not include a spread value. A small shadow can keep light text readable over a varied image when a plain background or overlay is not available.

.hero-title {
  color: #eef5f2;
  text-shadow: 0 2px 4px rgb(12 18 16 / 0.6);
}

The letters gain a narrow dark edge below them. Keep the effect close to the glyphs. Large offsets, bright colors, and heavy blur create duplicate-looking letter shapes that reduce readability. The text color still needs sufficient contrast against the actual background because a shadow does not replace accessible color contrast.

Follow Transparent Shapes with drop-shadow()

A transparent PNG or SVG can contain visible pixels in an irregular shape. box-shadow follows the image element's rectangular box, including its transparent corners. The drop-shadow() filter follows the alpha shape of the rendered image instead.

.logo-box {
  box-shadow: 0 0.5rem 0.75rem rgb(18 28 24 / 0.3);
}

.logo-shape {
  filter: drop-shadow(0 0.5rem 0.75rem rgb(18 28 24 / 0.3));
}

The first rule produces a rectangle around the asset. The second traces the visible logo shape. For transparent artwork, this is the modern CSS replacement for generating a separate shadow image on the server. Unlike box-shadow, drop-shadow() does not accept a spread radius or inset.

Common Pitfalls & Debugging

Mixing Up Spread and Blur

Symptom: the shadow becomes too large when you only wanted a softer edge. Cause: the fourth length changed spread instead of blur. Fix: keep the third value for blur and reset the fourth value to zero or remove it.

A Shadow Animation Feels Slow

Symptom: a card stutters while its shadow changes. Cause: animating box-shadow can require repeated shadow painting. Fix: place the final shadow on a pseudo-element (::before creates an extra styling layer inside the card, and its content: "" line is what makes the layer render) and animate that layer's opacity.

.card {
  position: relative;
}

.card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 1rem 2rem rgb(18 28 24 / 0.2);
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease;
}

.card:hover::before { opacity: 1; }

The shadow geometry stays fixed while only its visibility changes, so the card's shadow fades in on hover instead of changing shape. The inherited radius follows the card's corners, and pointer-events: none keeps the generated layer from blocking links or buttons.

The Shadow Disappears into the Background

Symptom: the declaration exists but the edge still looks flat. Cause: the shadow color is too close to the surrounding surface, or the alpha is too low. Fix: inspect the actual parent background, then adjust color and opacity there rather than judging the shadow on an isolated swatch.

Light-Mode Shadows Look Wrong in Dark Mode

If your site has a dark mode, give its shadows and borders their own checked values.

Symptom: a dark shadow disappears on a dark surface or makes the card look dirty. Cause: the light theme's shadow was reused unchanged. Fix: reduce the dark shadow and add a lighter edge or border that separates adjacent dark surfaces.

@media (prefers-color-scheme: dark) {
  .card {
    border: 1px solid rgb(190 214 203 / 0.28);
    box-shadow: 0 0.5rem 1.25rem rgb(3 8 6 / 0.35);
  }
}

The lighter border separates the card from the dark surface, while the reduced shadow keeps a small sense of depth.

Conclusion

Useful shadows combine a clear offset, controlled blur, and restrained color that works on the actual page surface. Continue with the color guide to tune shadow tones and opacity for each color scheme.

Frequently Asked Questions

What is the difference between box-shadow and drop-shadow()?

box-shadow follows an element's border box, including its border-radius, and supports spread, inset, and comma-separated layers. drop-shadow() follows the rendered alpha shape, which fits transparent PNG and SVG artwork with visible pixels that do not fill the border box.

How do you make a shadow appear on all sides?

Set both offsets to zero, then add a blur radius or a positive spread radius. The shadow expands evenly around the box instead of leaning toward one edge.

Why does the shadow look too dark?

A box shadow without an explicit color uses the element's current text color, which may be darker than expected. Set a shadow color with a lower alpha value, then check it against the actual light and dark surfaces.

What should you check for text-shadow accessibility?

Check the text color against its actual background without counting the shadow as contrast. Keep the blur and offset small enough that letter shapes stay clear, and test the result in every supported color scheme.

MDN Resources

Sources

  1. [1]
    box-shadow
    (developer.mozilla.org)
  2. [2]
    text-shadow
    (developer.mozilla.org)
  3. [3]
    drop-shadow()
    (developer.mozilla.org)