Market
Pricing
Sign inStart
Field GuideCSS and web
CSS and web

CSS box shadow: soft, layered shadows that look real

A single grey box-shadow is the quickest way to make an interface look like a template. Real shadows are several shadows at once, tinted, and quieter than you think.

GradientlyVerified Gradiently account·October 1, 2026·5 min read
Cover: Tape Cliff · GR·CY1K·VC

The short version

  • The CSS box shadow property takes an optional inset keyword, two offsets, a blur radius, a spread radius and a colour.
  • A single box shadow looks artificial because real objects cast a sharp contact shadow and a wide soft one at the same time.
  • Layering three to five shadows, each with roughly double the offset and blur of the last and a low opacity, produces a soft, believable shadow.
  • Tinting the shadow towards the background's hue looks more natural than pure black on coloured or warm surfaces.
  • Animating box-shadow repaints on every frame, so fade the opacity of a pseudo-element that carries the larger shadow instead.
On this page
box-shadow syntaxWhy a single shadow looks fakeHow to layer box shadowsAn elevation scale for an interfaceShadows on coloured and dark backgroundsAnimating box-shadow without jankWhen to skip the shadow

A CSS box shadow is written box-shadow: 0 4px 12px rgb(0 0 0 / 0.15): a horizontal offset, a vertical offset, a blur radius, an optional spread and a colour. That single shadow is fine for a quick prototype, but it rarely looks real. The shadows that convince the eye are layered: several shadows on one element, each larger and fainter than the last, tinted to suit the surface beneath.

box-shadow syntax

css
/* inset? | offset-x | offset-y | blur | spread | color */
.card { box-shadow: 0 4px 12px 0 rgb(0 0 0 / 0.15); }

/* Negative spread tucks the shadow under the box */
.card { box-shadow: 0 12px 24px -8px rgb(0 0 0 / 0.25); }

/* Inset: a shadow inside the box, like a pressed well */
.well { box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.12); }

/* Spread only: a hairline that follows border-radius */
.chip { box-shadow: 0 0 0 1px rgb(15 23 42 / 0.08); }

/* Several shadows, comma separated, first on top */
.card { box-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 8px 16px rgb(0 0 0 / 0.08); }
The shadow always follows border-radius, which is why a spread only shadow makes a neat rounded hairline without changing layout.
insetOff

Effect

Draws the shadow inside the border instead of outside
Offset x and y

Default

Required

Effect

Moves the shadow. Light from above means a positive y
Blur0

Effect

Softens the edge. Larger values spread the fade wider
Spread0

Effect

Grows or shrinks the shadow before blurring. Negative tucks it in
Colourcurrentcolor

Effect

Use an alpha colour; opaque shadows look like stickers
ValueDefaultEffect
insetOffDraws the shadow inside the border instead of outside
Offset x and yRequiredMoves the shadow. Light from above means a positive y
Blur0Softens the edge. Larger values spread the fade wider
Spread0Grows or shrinks the shadow before blurring. Negative tucks it in
ColourcurrentcolorUse an alpha colour; opaque shadows look like stickers
Spread is the value most people skip and the one that fixes the most. A small negative spread stops a big blur from leaking out of the sides.

Why a single shadow looks fake

Put a book on a table and look at its shadow. Right at the edge where it touches, the shadow is dark and crisp. Further out it becomes wide and faint, because light wraps around the object from many directions. One box-shadow can only be one of those things at a time: tight and hard, or wide and blurry. Either way the eye notices something is missing.

Single shadow

  • One medium blur in 25 percent black
  • Grey halo all round the card
  • Hard to tell how high the card floats
  • Looks dirty on coloured backgrounds

Layered shadow

  • Four shadows, each fainter and wider
  • Dense near the edge, airy far away
  • Height reads at a glance
  • Tinted to sit naturally on its surface

How to layer box shadows

The method is simple: start small and sharp, then double the offset and blur for each extra layer, keeping every layer faint. The opacities add up where the layers overlap, so individual values can be surprisingly low.

  1. 1

    Start with a contact shadow

    0 1px 1px at around 8 percent opacity. This is the dark line right under the edge.

  2. 2

    Double for each layer

    Then 0 2px 2px, 0 4px 4px, 0 8px 8px. Offset and blur grow together, so light seems to come from above.

  3. 3

    Keep each layer faint

    Between 5 and 10 percent each. If the result looks heavy, remove the largest layer before turning anything darker.

  4. 4

    Tint the colour

    Replace black with a deep version of the background hue, such as a navy on blue pages or a brown on cream.

css
:root {
  /* Shadow colour as channels, so opacity can vary per layer */
  --shadow-color: 222 47% 11%;

  --shadow-sm:
    0 1px 1px hsl(var(--shadow-color) / 0.08),
    0 2px 2px hsl(var(--shadow-color) / 0.06);

  --shadow-md:
    0 1px 1px hsl(var(--shadow-color) / 0.08),
    0 2px 2px hsl(var(--shadow-color) / 0.07),
    0 4px 4px hsl(var(--shadow-color) / 0.06),
    0 8px 8px hsl(var(--shadow-color) / 0.05);

  --shadow-lg:
    0 1px 1px hsl(var(--shadow-color) / 0.07),
    0 2px 2px hsl(var(--shadow-color) / 0.07),
    0 4px 4px hsl(var(--shadow-color) / 0.06),
    0 8px 8px hsl(var(--shadow-color) / 0.06),
    0 16px 16px hsl(var(--shadow-color) / 0.05),
    0 32px 32px hsl(var(--shadow-color) / 0.04);
}

.card { box-shadow: var(--shadow-md); }
.dialog { box-shadow: var(--shadow-lg); }
A three step elevation scale as custom properties. Change --shadow-color once and every shadow in the system follows.

Storing shadows as variables turns them into design tokens, which keeps every card, menu and dialog consistent. CSS variables for theming and design tokens show how to wire them into a full theme.

An elevation scale for an interface

Shadow is a signal of height, so it should mean the same thing everywhere. Pick three or four levels and assign them to jobs. Anything that floats higher gets a bigger, softer shadow, never just a darker one.

0none, or a 1px spread hairline

Use it for

Inputs, table rows, flat cards on a tinted page
1--shadow-sm

Use it for

Resting cards, buttons that should feel pressable
2--shadow-md

Use it for

Hovered cards, dropdown menus, popovers
3--shadow-lg

Use it for

Dialogs, sheets, anything over a dimmed page
LevelTokenUse it for
0none, or a 1px spread hairlineInputs, table rows, flat cards on a tinted page
1--shadow-smResting cards, buttons that should feel pressable
2--shadow-mdHovered cards, dropdown menus, popovers
3--shadow-lgDialogs, sheets, anything over a dimmed page
Four levels cover almost every interface. More than that and people stop being able to tell them apart.

Shadows on coloured and dark backgrounds

Pure black shadows turn grey and muddy on a coloured surface, the same way black mixed into paint dulls it. Take the shadow colour from the background instead: a deep teal shadow on a mint page, a dark plum on a lilac one. On gradient backgrounds, pick the darkest stop of the gradient and use it at low opacity.

Three page colours, each with the shadow colour that suits it. Use the shadow tones at 5 to 10 percent per layer.

In dark mode, shadows almost disappear, because there is little light for them to block. Show height by making raised surfaces slightly lighter instead, and keep a faint hairline at the edge. Dark mode design goes through the surface colours. For panels that float over imagery, a backdrop filter often separates layers better than a heavier shadow.

Animating box-shadow without jank

Changing box-shadow on hover forces the browser to repaint the shadow on every frame of the transition. On one button that is fine. On a grid of fifty cards it stutters. The standard fix is to put the larger shadow on a pseudo-element and fade its opacity, which the browser can composite cheaply.

css
.card {
  position: relative;
  box-shadow: var(--shadow-sm);
}

.card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transition: opacity 200ms ease-out;
  pointer-events: none;
}

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

@media (prefers-reduced-motion: reduce) {
  .card::after { transition: none; }
}
Both shadows are painted once. Hover only changes opacity, so nothing repaints during the transition.

More on what costs paint time is in CSS gradient performance, and the same layering approach suits buttons in gradient buttons in CSS.

When to skip the shadow

Shadows separate a surface from what is behind it. If the background is a rich gradient or photograph, a strong shadow fights it. Often a quieter background does more for the card than any shadow could. Gradiently's Marks are built for that: each one is a living background with its own colours, light and grain, and the words you place over it stay readable because the Mark calms itself behind them, with no boxes or shadows doing the work.

Find a gradient that’s only yours

Browse thousands of Marks, design with any of them for free, and claim the one that feels like you.

Browse the Market

Questions people ask

What are the values in CSS box-shadow?

An optional inset, a horizontal offset, a vertical offset, a blur radius, a spread radius and a colour, for example 0 4px 12px 0 rgb(0 0 0 / 0.15).

How do I make a soft box shadow in CSS?

Layer three to five shadows, doubling the offset and blur each time, at 5 to 10 percent opacity each. Tint them towards the background colour.

How do I add more than one box shadow?

Separate them with commas in one box-shadow declaration. The first shadow listed is drawn on top.

What does spread do in box-shadow?

It grows or shrinks the shadow before it is blurred. A negative spread keeps a large blur from showing at the sides of the box.

Is animating box-shadow bad for performance?

It repaints every frame. Put the hover shadow on a pseudo-element and transition its opacity instead.

Share this guide

Written by GradientlyVerified Gradiently account

The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.

See our profile

On this page

box-shadow syntaxWhy a single shadow looks fakeHow to layer box shadowsAn elevation scale for an interfaceShadows on coloured and dark backgroundsAnimating box-shadow without jankWhen to skip the shadow

Share this guide

Next in CSS and web

color-mix() in CSS: blending colours in the browser

Keep reading

All CSS and web guides
CSS and web

Backdrop filter in CSS: frosted glass that stays fast

5 min read

Frosted glass takes three lines of CSS and can still make a good phone stutter. Here is the code, what it costs to draw, and the fallbacks that keep words readable when the blur never arrives.

CSS and web

CSS text shadow: syntax, examples and better options

5 min read

text-shadow is a small property with a big reputation for rescuing unreadable headlines. It is better at decoration than rescue. Here is the syntax, a handful of styles worth copying, and what to use when legibility is the real problem.

CSS and web

Design tokens: one source of truth for colour and type

5 min read

A brand colour that lives in forty files will drift. A token lives in one place and every platform reads from it. Here is how to structure design tokens, name them so they last, and ship them to code and design tools.

Find the look that’s only yours.

Every Mark is a living background with one owner. Try an unclaimed Mark, and claim yours when it feels right.

Start designing

Check a certificate

Type a certificate number or a Mark’s code to see who owns it.

Works with ChatGPT and Claude

Ask your AI for a design. It makes it in your Mark, ready to edit.

ChatGPTClaude
How to connect

Product

  • Market
  • Explore
  • Pricing
  • Certificates

Learn

  • Field Guide
  • Help centre
  • What’s new

Developers

  • Overview
  • ChatGPT and Claude
  • API reference
  • API keys

Company

  • About
  • Contact us
  • Sign in

Legal

  • Terms
  • Privacy
  • Refunds
  • Mark protection
  • Cookies
  • All policies
Move through the letters. Each one wears a real Mark.Touch a letter to see its Mark.One name. Ten Marks inside it.
© 2026 Gradiently