A box-shadow generator that thinks in layers, not one value
Part of CSS LabKit, built by GrowQuest, leaders in web experiences.
Most box-shadow generators give you five sliders — X, Y, blur, spread, colour — and a single line of CSS. That's fine until you want a shadow that actually looks like light falling on a surface, because real depth almost never comes from one shadow. It comes from two or three, stacked: a tight, dark contact shadow close to the edge, and a soft, faint one spreading further out. Try to write that by hand and you're juggling commas in a box-shadow value with no visual feedback until you reload the page.
ShadowLab treats each shadow as its own object — add one, duplicate it, drag it above or below another, mute it, delete it — and renders the full stack live on a card as you work. The light-direction dial repositions every unlocked layer at once, so a three-layer shadow keeps reading as one consistent light source instead of three shadows pointing three different ways. It's built for the case a five-slider tool can't reach: shadows with actual depth, not just an outline with blur.
Layers, not one value
Every shadow is its own object you can reorder, mute, blend and duplicate — not a fixed set of five numbers.
One light source, enforced
The light-direction dial keeps every layer's angle consistent, so a multi-layer shadow reads as one falloff, not several shadows pointing different ways.
Barebones CSS by default
Comments, custom properties and prefixes stay off until you switch them on. What you copy is the shadow.
What you can do with ShadowLab
box-shadow layers as the effect needs, each with independent X/Y offset, blur, spread, colour, alpha, inset/outer state and blend mode.box-shadow value straight in and watch it decompose into editable layers.How browsers actually paint a box-shadow
Every control in the sidebar maps to something the rendering engine is actually computing. Knowing what happens between the numbers explains most of the surprises — the shadow that looks flat, the one that reads as two light sources, the animation that stutters.
The box-shadow syntax model
box-shadow takes up to five positional values plus an optional keyword: offset-x offset-y blur-radius spread-radius color inset. Offsets can be negative, blur and spread default to 0 if omitted, and inset flips the shadow to render inside the border edge instead of outside it. Comma-separate multiple shadow definitions on one element to stack layers.
Blur is a real blur, not a fade
The blur-radius isn't a linear opacity gradient — it's computed as an approximation of a Gaussian blur applied to the shadow shape. That's why softness doesn't scale linearly with the number: doubling blur radius roughly doubles the blur's standard deviation, so perceived softness grows faster than the value suggests, especially past 20–30px.
Blur vs. spread — different geometry
Blur softens the edge of the shadow shape without changing its size. Spread grows or shrinks the shape itself in every direction before blur is applied, the same way a border width would. A tight, low-blur, low-spread shadow reads as contact; a large spread with heavy blur reads as ambient glow from a distant source.
Inset vs. outer compositing order
An outer shadow paints behind the element's background and border box. An inset shadow paints on top of the background but behind the border and content — which is what makes it read as carved-in rather than cast. Stack an inset and an outer shadow on the same element and you get both effects at once, the basis of most neumorphic UI.
Why stacked shadows read as one light source
Light from a single source produces a shadow whose blur grows roughly proportionally to how far the shadow point is from the object, along a constant direction. Faking that with stacked layers means holding every layer's offset angle consistent while blur grows with offset distance — break that ratio and the eye reads two competing lights instead of one falloff.
Performance: paint, not layout — but blur is the expensive part
box-shadow doesn't affect element geometry, so changing it triggers a paint, not a layout reflow. But animating the shadow's own numbers — especially blur-radius — forces that blur to recompute every frame, which gets expensive on large elements. transform and opacity are the two properties the compositor can animate without repainting, so cross-fading between two pre-built shadow states is usually cheaper than tweening blur directly.
Anatomy of the CSS behind a stacked shadow
A realistic three-layer elevation shadow, close to what a card preset in the tool produces:
.card {
box-shadow:
0 1px 2px rgba(15, 23, 42, 0.08),
0 4px 8px rgba(15, 23, 42, 0.10),
0 12px 24px rgba(15, 23, 42, 0.12);
}0 1px 2px rgba(…, .08)The contact layer — near-zero offset, a small blur, low alpha. This is the tight, dark edge where the object nearly touches the surface.0 4px 8px rgba(…, .10)The mid layer — larger offset and blur, slightly higher alpha, filling the gap between contact and ambient.0 12px 24px rgba(…, .12)The ambient layer — largest offset and blur, softest edge. This is the layer that gives the shadow its overall lift.comma-separated listThe first shadow listed paints closest to the viewer, and later ones paint progressively behind it — the reverse of how z-index usually reads.rgba(15, 23, 42, …)A dark navy, not pure black. Tinting shadow colour toward the background's hue is what keeps a shadow from reading as a flat grey smudge.no inset keywordAll three layers are outer shadows, so the card reads as raised. Add inset to any layer to carve part of the same stack inward instead.Best practices & design principles
Pick one light-source angle and use it everywhere
If buttons cast shadows down-right and cards cast them straight down, the interface reads as inconsistent even if nobody can say why. Set an angle once and keep every shadowed element on it — the whole reason the light-direction dial exists.
Use an elevation scale instead of ad-hoc numbers
Material Design's dp system pairs a tight key shadow with a soft ambient one, both scaling together as elevation increases. Even a simplified four- or five-step version keeps a whole app's depth cues legible as it grows.
Reach for inset shadows to say “pressed,” not for general depth
An inset shadow reads as a surface pushed inward — active button states, input fields, toggle tracks. Using it for anything that should look raised fights the reading users already bring from real materials.
Tint shadow colour toward the surface, not pure black
A #000-based shadow on a coloured background looks like a grey stain. Shifting the shadow's hue a few degrees toward the background or accent colour reads as a shadow of that surface, not one slapped on top.
Don't put a 40px blur on a 24px icon
Blur radius needs to stay proportional to the element casting the shadow — oversized blur on small components just looks like haze. A rough starting point: keep blur under about half the element's smallest dimension.
Animate cheap properties, not the shadow's own numbers
If a shadow needs to grow on hover, cross-fade between two pre-built box-shadow states with opacity, or transition a pseudo-element, rather than animating blur or spread directly.
Why ShadowLab exists
Every box-shadow generator out there does one of two things well: it exposes the raw numbers for a single shadow, or it hands you a fixed, pre-styled effect you can tweak within narrow limits. Neither gets you to a genuinely layered shadow — the kind with a tight contact layer and a soft ambient layer that read as one light source — without leaving the tool and hand-editing commas in a CSS value.
ShadowLab is built around the idea that a shadow is a small stack, not a single value, and that stack deserves the same editing tools as anything else you build in layers: reorder, duplicate, mute, blend modes, per-layer colour. It's meant to be the most complete shadow editor available, equally usable for a first shadow and a twentieth design-system elevation scale, and every export stays exactly what you asked for — nothing extra bolted on.
If you're new to this
Start from a visual-mode preset — Soft elevation or Material depth are the easiest to reason about — and watch what each layer's X/Y/blur/spread does to the preview before touching numbers yourself.
If you do this daily
Import an existing box-shadow value straight from your codebase, decompose it into layers, and use the light-direction dial and elevation slider to bring inconsistent legacy shadows onto one scale fast.
Honest output
Every toggle for comments, custom properties, prefixes and minification starts off. What you copy is the shadow, not extras you didn't ask for.
A better alternative to CSSmatic, Josh Comeau's Shadow Palette Generator and neumorphism.io
These are three of the best-known tools for CSS shadows, and each is genuinely good at what it was built for. Here's where ShadowLab differs structurally, without knocking what they do well.
Frequently asked questions about CSS box-shadows
What's the difference between box-shadow and filter: drop-shadow()?
box-shadow follows the element's box geometry — on a transparent PNG it casts a rectangular shadow regardless of the visible pixels. filter: drop-shadow() follows the actual rendered alpha shape, so it hugs a transparent PNG or SVG's visible silhouette instead. drop-shadow() also doesn't accept a spread value or multiple comma-separated shadows the way box-shadow does.
How do I add multiple shadows to one CSS element?
Comma-separate multiple shadow definitions inside a single box-shadow declaration: box-shadow: 0 1px 2px rgba(0,0,0,.1), 0 8px 16px rgba(0,0,0,.1);. The first-listed shadow paints on top and each subsequent one paints behind it — the opposite order from how z-index usually reads.
Does using box-shadow hurt performance?
It doesn't trigger layout, so it's cheaper than properties like width or margin. But it does trigger a paint, and animating the shadow's own values — especially blur radius — forces that paint to recompute every frame, which can stutter on larger elements. Animating opacity or transform between two pre-built shadow states is generally cheaper than tweening the shadow numbers directly.
What's the difference between blur and spread in box-shadow?
Blur softens the edge of the shadow shape without resizing it. Spread grows or shrinks the shape itself in every direction before blur is applied, similar to adding a border. Spread with no blur gives a soft-edged size increase with hard boundaries; blur with no spread keeps the original size with a feathered edge.
How do inset shadows work?
Adding the inset keyword paints the shadow inside the element's border box instead of outside it. It composites on top of the background but behind the border and content, which is why it reads as a surface pushed in — the standard technique for pressed buttons, active toggles and input wells.
How do I recreate a Material Design elevation shadow?
Material's elevation model pairs two shadow layers per surface — a tighter, darker key shadow close to the edge, and a softer, lighter ambient shadow spreading further — with both scaling together as elevation (in dp) increases. That pairing is exactly what the elevation slider automates.
Why do my stacked shadows look muddy instead of like one shadow?
Usually the offset angle isn't consistent across layers, or the blur-to-distance ratio jumps around instead of increasing smoothly. Both break the illusion of a single light source. Locking every layer's angle and letting blur grow with offset distance usually fixes it.
Can box-shadow colour use transparency?
Yes — any CSS colour syntax with an alpha channel works (rgba(), hsla(), 8-digit hex, or color-mix()), and alpha is normal practice for shadows: a fully opaque shadow colour almost always looks too heavy against a real background.
How many box-shadow layers can one element have?
As many as you list, comma-separated — there's no hard spec limit, only practical rendering cost and diminishing visual returns. Most convincing depth effects use two or three layers rather than many more.