CSS Effects April 2026 · 8 min read

CSS Box Shadow Techniques: Beyond the Basics

A single box-shadow creates a generic look. Layered, colored, and inset shadows create sophisticated depth and personality. Here's how professionals do it.

Simple

Basic

Layered

Layered (3 layers)

Glow

Purple Glow

Neumorphic

Neumorphism

Inset

Inset / Pressed

The box-shadow Syntax

box-shadow: [inset] offset-x offset-y [blur] [spread] color;

Technique 1: Layered Shadows for Real Depth

A single shadow looks flat and generic. Layering 2–3 shadows at different blur radii and opacities produces the depth you see in professional design systems:

/* Material Design-inspired elevation */
.card--elevated {
  box-shadow:
    0 1px 2px  rgba(0, 0, 0, 0.30),
    0 4px 8px  rgba(0, 0, 0, 0.25),
    0 16px 32px rgba(0, 0, 0, 0.20);
}

The smallest shadow adds the crisp edge detail; the largest creates the ambient shadow. Together they feel physically convincing.

Technique 2: Colored Glow Effects

Using a colored shadow with no offset creates a glow. Pair it with a spread for extra intensity:

/* Purple glow for a primary button */
.btn-primary {
  background: #7c5cfc;
  box-shadow:
    0 0 0 0 transparent,           /* ring-offset */
    0 8px 24px rgba(124,92,252,0.45),  /* ambient glow */
    0 0 60px rgba(124,92,252,0.15);    /* distant halo */
  transition: box-shadow 0.3s ease;
}
.btn-primary:hover {
  box-shadow:
    0 0 0 0 transparent,
    0 12px 32px rgba(124,92,252,0.65),
    0 0 80px rgba(124,92,252,0.25);
}

Technique 3: Neumorphism (Soft UI)

Neumorphism creates an extruded-from-surface look using two shadows — one light, one dark — offset in opposite directions. The element's background must match the container background exactly:

:root { --bg: #e0e5ec; }

.neomorphic {
  background: var(--bg);
  border-radius: 16px;
  box-shadow:
     8px  8px 16px #b8bec7,   /* dark shadow (bottom-right) */
    -8px -8px 16px #ffffff;   /* light shadow (top-left) */
}

/* Pressed/active state */
.neomorphic:active {
  box-shadow:
    inset  6px  6px 12px #b8bec7,
    inset -6px -6px 12px #ffffff;
}

Accessibility warning: Neumorphism often produces very low contrast between the element and background. Always verify your contrast ratios pass WCAG AA — use StudioLimb's Contrast Checker.

Technique 4: Inset Shadows for Depth

Inset shadows create a sunken or engraved appearance, great for input fields, wells, and pressed button states:

/* Dark mode input field */
.input {
  background: rgba(0, 0, 0, 0.3);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.6),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}

.input:focus {
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.6),
    0 0 0 2px rgba(124, 92, 252, 0.6);
}

Technique 5: Negative Spread for Tight Shadows

A negative spread radius shrinks the shadow, letting you create a shadow that only appears below the element (like it's floating above a surface) without spreading to the sides:

/* Shadow appears only below, not on sides */
.floating {
  box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
  /*                        ^^^  negative spread */
}

Performance: box-shadow vs filter: drop-shadow()

box-shadow is GPU-accelerated in all modern browsers and is the right choice for most cases. filter: drop-shadow() follows the element's alpha channel (including transparent PNG cutouts) but is more expensive and shouldn't be used on animating elements. Stick with box-shadow unless you specifically need the shape-following behavior.

Transition Tip

Avoid animating box-shadow on scroll or hover for many elements simultaneously. Instead, animate opacity on a pseudo-element that has the target shadow — this is GPU-composited and far smoother.

/* Performant shadow hover animation */
.card {
  position: relative;
  transition: transform 0.2s ease;
}

.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.card:hover {
  transform: translateY(-4px);
}

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

Technique 6: Box Shadow as a Border (Zero-Blur Trick)

Using box-shadow with zero blur and zero offset creates a border-like outline that doesn't affect layout (unlike border, which adds to the element's box model). This is useful for adding multiple "borders" or for creating focus rings without layout shift:

/* Doesn't affect layout, can be stacked */
.multi-border {
  box-shadow:
    0 0 0 2px #7c5cfc,       /* inner ring */
    0 0 0 5px rgba(124,92,252,0.2); /* outer glow ring */
}

/* Focus ring without layout shift */
.btn:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px #050508,    /* offset gap */
    0 0 0 4px #7c5cfc;    /* visible ring */
}

Building a Shadow Design Token System

Professional design systems define shadows as named tokens, not one-off values. This ensures consistency across components and makes global updates trivial:

:root {
  /* Elevation scale */
  --shadow-xs:  0 1px 2px rgba(0,0,0,0.2);
  --shadow-sm:  0 1px 2px rgba(0,0,0,0.2), 0 4px 8px rgba(0,0,0,0.15);
  --shadow-md:  0 1px 2px rgba(0,0,0,0.25), 0 8px 16px rgba(0,0,0,0.2), 0 20px 40px rgba(0,0,0,0.15);
  --shadow-lg:  0 2px 4px rgba(0,0,0,0.3), 0 16px 32px rgba(0,0,0,0.25), 0 40px 80px rgba(0,0,0,0.2);

  /* Semantic shadows */
  --shadow-card:   var(--shadow-sm);
  --shadow-modal:  var(--shadow-lg);
  --shadow-button: var(--shadow-xs);

  /* Colored glows */
  --shadow-primary-glow: 0 8px 24px rgba(124,92,252,0.4);
  --shadow-danger-glow:  0 8px 24px rgba(239,68,68,0.4);
}

/* Usage */
.card   { box-shadow: var(--shadow-card); }
.modal  { box-shadow: var(--shadow-modal); }
.btn-cta { box-shadow: var(--shadow-primary-glow); }

Common Box Shadow Mistakes

1. Single shadow on everything

One shadow at one blur radius looks flat and generic — the same shadow Photoshop users have been applying since 2006. Use 2–3 layers at different blur radii. The extra two lines of CSS are always worth it.

2. Black shadows on dark backgrounds

Pure black shadows (rgba(0,0,0,X)) on dark backgrounds look muddy. Add a subtle hue that matches the surface color: a dark blue background should use dark blue-tinted shadows (rgba(10,10,30,X)).

3. Spread radius making shadows too large

A large positive spread pushes the shadow far beyond the element's edges, creating an ugly halo. Use spread at 0 or negative values for most cases. Reserve positive spread for intentional glow effects.

4. Animating box-shadow directly

Animating box-shadow forces the browser to repaint on every frame — expensive on complex pages. Use the pseudo-element opacity technique above for hover transitions instead.

5. Forgetting dark mode

Shadows that look great on white backgrounds are often invisible on dark backgrounds. Build your shadow tokens separately for light and dark modes, or use @media (prefers-color-scheme: dark) to swap them.

Frequently Asked Questions

What's the maximum number of box-shadow layers I should use?

3 layers is almost always sufficient and covers all real-world needs. Beyond that, the visual difference is imperceptible to most users and the performance cost isn't worth it. Google's Material Design uses exactly 3 elevation layers for its shadow system.

Can I use box-shadow on inline elements?

Yes, but it behaves differently — it renders on each line box separately when text wraps. For inline text highlights, consider background with a padding hack instead, as it gives you more control over the shape.

Is box-shadow or filter: drop-shadow() better for performance?

box-shadow is composited on the GPU in all modern browsers and is the better choice for most use cases. filter: drop-shadow() follows the element's alpha mask (useful for cutout PNGs), but it's repainted on the CPU and should not be used on animated elements.

How do I create a bottom-only shadow?

Use a negative spread radius to prevent the shadow from appearing on the sides: box-shadow: 0 20px 30px -15px rgba(0,0,0,0.5). The -15px spread pulls the shadow inward, making it appear only at the bottom.

Related Guides

shadow

Box Shadow Generator

Build layered shadows visually

contrast

Contrast Checker

Verify neumorphism accessibility