Articles /

color

box-shadow: the complete reference

From basic depth to layered realism — every shadow technique in modern CSS.

Anatomy of box-shadow

The box-shadow shorthand takes offset-x, offset-y, blur-radius, spread-radius, and color. All but offset-x and offset-y are optional:

/* Simple drop shadow */
.card { box-shadow: 0 4px 12px oklch(0 0 0 / 0.1); }

/* With spread — expands the shadow before blur */
.card { box-shadow: 0 4px 12px 2px oklch(0 0 0 / 0.08); }

/* Inset — shadow inside the element */
.input:focus { box-shadow: inset 0 0 0 2px oklch(0.52 0.22 265); }

/* Multiple shadows — comma-separated */
.card {
  box-shadow:
    0 1px 2px oklch(0 0 0 / 0.06),
    0 4px 12px oklch(0 0 0 / 0.08),
    0 12px 40px oklch(0 0 0 / 0.04);
}

Layered shadows for realism

A single shadow looks flat. Stacking multiple shadows at different blur radii creates a more natural depth effect, mimicking how real objects cast both a tight contact shadow and a diffuse ambient shadow:

/* Elevation level 2 — medium lift */
--shadow-md:
  0 1px 3px oklch(0 0 0 / 0.08),
  0 4px 16px oklch(0 0 0 / 0.1);

/* Elevation level 3 — high lift (modals, dropdowns) */
--shadow-lg:
  0 2px 4px oklch(0 0 0 / 0.04),
  0 8px 24px oklch(0 0 0 / 0.1),
  0 24px 48px oklch(0 0 0 / 0.06);

Store these as custom properties and reference them across your design system for consistent elevation.

OKLCH shadows

Using oklch() for shadow colors gives perceptually consistent opacity. oklch(0 0 0 / 0.1) looks the same lightness-reduction whether the background is white or colored. This matters for colored card backgrounds or dark mode, where rgba(0,0,0,0.1) can look inconsistent.

Shadows in dark mode

Shadows need to be stronger in dark mode because there's less contrast between a shadow and a dark background. Increase the alpha value — typically doubling it:

:root {
  --shadow-md: 0 4px 16px oklch(0 0 0 / 0.1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --shadow-md: 0 4px 16px oklch(0 0 0 / 0.35);
  }
}

Performance

box-shadow triggers paint but not layout. Animating it (e.g., on hover) is relatively cheap but not compositor-only. For smoother hover transitions, animate opacity on a pseudo-element with the shadow already applied — this keeps the animation on the compositor thread.

Avoid animating box-shadow directly on many elements. Instead, set the shadow statically and transition opacity on a ::after pseudo-element that holds the larger shadow.