Box-shadow generator
Build a CSS box-shadow visually, with as many layers as you need: offsets, blur, spread, colour with opacity and inset, over a preview whose box colour, background and border radius you set yourself.
Shadow layers
A card at rest on the page.
2 of 10. The first layer is painted on top.
Layer 1 of 2
Preview
The preview box and its background are yours to set. They are not part of the generated CSS.
Generated CSS
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 2px 4px -1px rgba(0, 0, 0, 0.1);
Build a CSS box-shadow visually, with as many layers as you need: offsets, blur, spread, colour with opacity and inset, over a preview whose box colour, background and border radius you set yourself.
On this page
How to use this tool#
- Start from a preset or from the default layer. The presets cover the shapes people actually ship: three elevation levels, a layered smooth shadow, a hard offset, an inset well and a glow. Any of them is a starting point, not a finished value.
- Pick a layer in the list, then move the sliders. Every layer has its own horizontal and vertical offset, blur radius, spread radius, colour, opacity and
insetswitch. Each slider is paired with a number field if you would rather type the value. - Add, duplicate, reorder and hide layers. The first layer in the list is painted on top. The eye button takes a layer out of the output without deleting it, which is the quickest way to see what one layer is contributing.
- Set the preview up like your real page. The box colour, the background colour and the border radius are all yours to change. A shadow that looks right on white can disappear on a mid-grey, so judge it on the surface you will actually use.
- Copy the CSS. The output box carries the whole declaration, one layer per line once there is more than one. The colour format switch writes transparency as
rgba()or as eight-digit hex. - Paste an existing value to edit it. Drop a
box-shadowfrom your stylesheet into the field under the output and it is split back into editable layers.
Nothing is uploaded and nothing is stored. The page is static files, the tool is JavaScript running on your machine, and it keeps working with the network disconnected.
What box-shadow does#
box-shadow attaches one or more drop shadows to a box. Each shadow is the shape of the element's border box, offset, optionally blurred, optionally grown or shrunk, and painted in a colour of your choosing. It is defined in CSS Backgrounds and Borders Module Level 3, and MDN lists it as widely available, supported across browsers since July 2015. No prefix, no fallback.
Two things it does not do are worth knowing up front. It does not affect layout: the spec says plainly that shadows "do not influence layout and may overlap (or be overlapped by) other boxes and text or their shadows", and that they "do not trigger scrolling or increase the size of the scrollable overflow area". And it is not inherited, so a shadow on a card is not repeated on everything inside it.
The syntax, value by value#
The formal grammar for a single shadow is short:
<shadow> = <color>? && [<length>{2} <length [0,ā]>? <length>?] && inset?The && means those three parts may appear in any order, so the colour and theinset keyword can sit before the lengths, after them, or on either side. The lengths themselves are ordered, and there are two to four of them:
box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.14) inset;
^ ^ ^ ^ ^ ^
| | | | | +-- inset (optional)
| | | | +-- colour (optional)
| | | +-- spread radius (optional)
| | +-- blur radius (optional, never negative)
| +-- vertical offset (down is positive)
+-- horizontal offset (right is positive)- Horizontal offset. A positive value draws the shadow to the right of the box, a negative one to the left.
- Vertical offset. Positive moves the shadow down, negative moves it up. These two are the only required values.
- Blur radius. Optional, and the spec is explicit that negative values are invalid. Zero gives a sharp edge.
- Spread radius. Optional. Positive grows the shadow in every direction, negative shrinks it.
- Colour. Optional. The spec says omitted colours "default to currentColor", which means the shadow silently follows the element's text colour. That is occasionally what you want and usually a surprise, so this tool always writes the colour out.
inset. Optional. It flips the shadow from outside the box to inside it.
Omitted lengths are zero. Percentages are not allowed anywhere in the value: these are lengths, so px, rem, em and the rest, but never 50%.
How blur really behaves#
The blur radius is not the distance the shadow fades over, and it is not the standard deviation of the blur either. The spec pins both numbers down: the result must approximate "a Gaussian blur with a standard deviation equal to half the blur radius", and the note that follows says the visible colour transition runs "approximately the twice length of the blur radius", is "perpendicular to and centered on the shadow's edge", and goes from almost the full shadow colour at the inner end to almost fully transparent at the outer end.
Read that geometrically and it explains the thing that trips people up. A 24px blur spreads its fade over roughly 48px, half of it outside the shadow's shape and half of it inside. The solid core of the shadow shrinks as the blur grows, so a large blur on a small box can leave you with a faint smudge and no shadow at all. The fix is spread, not more blur.
It also explains why big blurs cost more. Blur is a per-pixel operation over an area that grows with the square of the radius, and it has to run every time the shadow is repainted.
How spread really behaves#
Spread changes the size of the shadow shape before it is blurred. The spec describes it as outsetting the shadow's straight edges by the spread distance for an outer shadow, insetting them for an inner one, and flooring the resulting width and height at zero.
Three things it is good for:
- A solid ring.
0 0 0 3pxwith no blur and no offset is a ring the thickness of the spread, drawn outside the border box and holding the element's rounded corners. This is how most focus rings are built. - Pulling a soft shadow back under the box. A negative spread on a heavily blurred layer keeps the fade from creeping out at the sides, which is what gives a card a shadow that reads as contact with the surface rather than as fog.
- A shadow on one side only. Offset the shadow as far as you want it to show and give it a negative spread of about the same size, so the three other sides are pulled back under the box.
Spread and the corner radius#
Spread does not simply scale the shape, because that would distort the corners. The spec adjusts the radii instead: they are "increased (decreased, for inner shadows) from the border-box (padding-box) radii by adding (subtracting) the spread distance (and flooring at zero)".
There is one correction on top of that, and it is the part almost nobody knows. When the border radius is smaller than the spread distance, the spread is first multiplied by 1 + (r - 1)3, where r is the ratio of the border radius to the spread distance. The spec's own example: a 10px border radius with a 20px spread gives r = 0.5, so the shadow's corner radius is 10px + 20px × (1 + (0.5 - 1)3) = 27.5px rather than 30px. The adjustment exists so that a nearly square corner stays nearly square instead of suddenly rounding off, and it is applied to each dimension on its own.
The practical consequence: a square-cornered box keeps square shadow corners at any spread, and a large spread on a slightly rounded box will look a little tighter at the corners than the arithmetic suggests. That is correct behaviour, not a browser bug.
inset: the shadow on the inside#
The inset keyword turns the shadow inside out. The spec puts the two cases side by side:
- An outer shadow "casts a shadow as if the border-box of the element were opaque", and "is drawn outside the border edge only: it is clipped inside the border-box of the element".
- An inner shadow "casts a shadow as if everything outside the padding edge were opaque", and "is drawn inside the padding edge only: it is clipped outside the padding box of the element".
Two useful consequences fall out of that clipping. First, an outer shadow never shows through its own element, even when the element's background is semi-transparent, because the whole border box is knocked out of it. Second, an inner shadow sits on the padding box, so a thick border pushes it inwards and the shadow does not run under the border.
Their painting positions differ too. The spec puts outer shadows immediately below the element's background and inner shadows immediately above it, below the borders. An inset shadow therefore tints the background but never covers the text, which is what makes inset well shadows readable on inputs.
Layering shadows for depth that looks real#
A single shadow with one blur value is the reason so many shadows look like a sticker. Real shadows are two things at once: a tight, darker contact shadow where the object nearly touches the surface, and a wide, much fainter ambient shadow from the light in the room. Onebox-shadow declaration can carry both, as a comma-separated list.
Order matters. The spec says "the shadow effects are applied front-to-back: the first shadow is on top and the others are layered behind", so put the small, dark contact layer first and the wide, faint ambient layer last.
Two patterns are worth knowing:
- Two layers, different jobs. A tight layer such as
0 1px 2px rgba(0, 0, 0, 0.06)under a wide one such as0 8px 16px -4px rgba(0, 0, 0, 0.14). The negative spread on the wide layer keeps it under the box. This is what the Elevation presets above do. - A doubling ramp. Five or six layers whose offset and blur double each time while the opacity stays low and constant. No single layer has a visible edge, so the fade looks continuous rather than like a blur with a boundary. That is the Layered smooth preset.
Keep the opacities low. Each layer composites over the ones behind it, so six layers at 4% are not 4% of shadow, they are noticeably darker. It is the stack, not any one number, that you tune.
Shadows follow the border radius#
Because an outer shadow's perimeter has "the exact same size and shape as the border box", it inherits the border radius for free. There is nothing to synchronise: changeborder-radius and the shadow's corners follow, adjusted for spread by the rule above. Try it with the border radius slider in the preview.
The one thing that does not change the shape is border-image. The spec says so directly: "The border-image does not affect the shape of the box-shadow."
A shadow on a pseudo-element that has to match its parent's corners is the common case where people get this wrong. Use border-radius: inherit on the pseudo-element rather than repeating the value.
box-shadow or filter: drop-shadow()#
They look similar and they are not interchangeable. box-shadow shadows the box.filter: drop-shadow() shadows the alpha channel of what was actually painted: the Filter Effects spec describes it as "a blurred, offset version of the input image's alpha mask drawn in a particular color, composited below the image".
- Use
drop-shadow()when the visible shape is not a rectangle: a transparent PNG or SVG, an element withclip-path, a shape drawn with a mask, or a group of children you want shadowed as one silhouette. - Use
box-shadowfor cards, buttons, inputs, menus and rings, where the box is the shape and you want spread,insetor several layers.
The differences that bite. drop-shadow() has no inset and no spread: its grammar is drop-shadow( <color>? && <length>{2,3} ), and the spec notes that "spread values or multiple shadows are not accepted for this level of the specification". It is a filter, so it creates a containing block for fixed and absolutely positioned descendants and it applies to the whole subtree.
And the blur numbers are not the same number. The Filter Effects spec says values are interpreted as for box-shadow "but with the optional 3rd <length>value being the standard deviation instead of blur radius", while box-shadow's standard deviation is half its blur radius. So a drop-shadow() blur is about twice as soft as the same number in a box-shadow. Halve the value when you move one to the other.
/* Same softness, different number: box-shadow's blur
radius is twice drop-shadow's standard deviation. */
.box { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); }
.shape { filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.3)); }
/* drop-shadow follows the alpha channel, so a shadow
under a transparent PNG or a clipped shape works. */
.badge {
clip-path: polygon(50% 0, 100% 100%, 0 100%);
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4));
}Performance: animating shadows#
A shadow is cheap to paint once and expensive to repaint sixty times a second. Google's own animation guidance is blunt about the two properties that stay off the paint path: "restrict animations to opacity and transform to keep animations on the compositing stage", and it names blur directly, noting that "anything that involves a blur (like a shadow, for example) takes longer to paint than drawing a red box".
box-shadow is neither of those, so a transition: box-shadow 200ms on a hover repaints a blurred area on every frame. On a small button nobody will notice. On a grid of cards, or on anything animating during a scroll, it shows.
The standard fix is to paint the target shadow once, on a pseudo-element behind the box, and animate that element's opacity instead:
/* The shadow is painted once on a pseudo-element.
Only opacity changes, and opacity composites. */
.card {
position: relative;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.card::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.22);
opacity: 0;
transition: opacity 200ms ease;
}
.card:hover::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
.card::after { transition: none; }
}Shadows in dark mode#
A black shadow works on a light page because it is darker than the page. On a near-black page there is very little room left below the background, so the same value all but disappears. Copying a light-mode shadow scale into dark mode is the most common reason dark interfaces look flat.
Three adjustments, in the order they are worth trying:
- Lighten the surface instead. On dark backgrounds, raising a panel's own lightness separates it from the page more convincingly than any shadow. This is what the rest of this site does: it has two materials and no resting shadows at all.
- Go darker and wider, not lighter. If you do want a shadow, push the opacity up hard (0.4 to 0.6 rather than 0.1) and spread the blur further. A grey shadow on a dark background reads as haze.
- Add a lit top edge. A one-pixel inset highlight such as
inset 0 1px 0 rgb(255 255 255 / 0.08)suggests a light source above and does the separating work a shadow cannot.
Keep the values in custom properties and swap them under prefers-color-scheme, so the components themselves never know which scheme is active:
:root {
--shadow-card:
0 1px 2px rgba(0, 0, 0, 0.06),
0 2px 4px -1px rgba(0, 0, 0, 0.1);
--edge-top: none;
}
@media (prefers-color-scheme: dark) {
:root {
/* Darker and wider, because the contrast is smaller. */
--shadow-card:
0 2px 4px rgba(0, 0, 0, 0.5),
0 8px 20px -4px rgba(0, 0, 0, 0.6);
/* A lit top edge does the work the shadow cannot. */
--edge-top: 0 1px 0 rgba(255, 255, 255, 0.08) inset;
}
}
.card {
box-shadow: var(--shadow-card), var(--edge-top);
}Worked examples#
An elevation scale
Three levels, each a contact layer plus an ambient layer, held as custom properties so a component names a level rather than a value.
:root {
--shadow-1:
0 1px 2px rgba(0, 0, 0, 0.06),
0 2px 4px -1px rgba(0, 0, 0, 0.1);
--shadow-2:
0 2px 4px -1px rgba(0, 0, 0, 0.08),
0 8px 16px -4px rgba(0, 0, 0, 0.14);
--shadow-3:
0 4px 8px -2px rgba(0, 0, 0, 0.1),
0 20px 40px -10px rgba(0, 0, 0, 0.22);
}
.card { box-shadow: var(--shadow-1); }
.card:hover { box-shadow: var(--shadow-2); }
.dialog { box-shadow: var(--shadow-3); }Focus rings without blur
A spread with no blur gives a solid ring that follows the border radius and takes no space in the layout. Stack two of them for the white-gap ring that reads on any background.
/* A two-step focus ring: a white gap, then the ring.
Zero blur, so both are solid shapes. */
.button:focus-visible {
outline: none;
box-shadow:
0 0 0 2px #ffffff,
0 0 0 5px #2457c5;
}
/* One ring plus the resting shadow, kept in the same list. */
.input:focus-visible {
box-shadow:
0 0 0 3px rgba(36, 87, 197, 0.45),
0 1px 2px rgba(0, 0, 0, 0.06);
}One warning before you reach for this. In forced-colors mode the browser forcesbox-shadow to none, so a focus ring built only from shadows vanishes completely for the people who most need to see it. outline survives, it takes its colour from the forced palette, and it follows the border radius in current browsers. Makeoutline the real focus indicator and treat a shadow ring as decoration on top of it, or keep an outline inside a forced-colors media query as the fallback.
Inset wells and pressed states
An inner shadow under the top edge plus a one-pixel light line under the bottom edge is the whole trick behind a field that looks recessed.
/* An input well: the light source is above, so the
shadow sits under the top edge. */
.field {
background-color: #f3f4f1;
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.22) inset,
0 1px 0 rgba(255, 255, 255, 0.6);
}
/* A pressed button: the inner shadow deepens on :active. */
.button:active {
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3) inset;
}Questions people ask#
Why is my box-shadow not showing?
Five causes cover nearly every case. An ancestor with overflow: hidden clips it, since shadows are painted content like anything else. The blur is large and the opacity is low, so the shadow is technically there and visually nothing. The element has no size, because an empty inline element has no box to shadow. It is an outer shadow behind a fully transparent background, in which case it is drawn, but you are looking at the knocked-out area: it is clipped inside the border box. Or the machine is in forced-colors mode, where the browser forcesbox-shadow to none outright.
Can a box-shadow take a percentage?
No. Every value in the grammar is a <length>, so px,rem, em, ch and the rest work, and percentages are invalid. Using em makes a shadow scale with the component's font size, which is occasionally exactly what you want.
Does a shadow make the element bigger?
No. Shadows do not influence layout and do not increase the scrollable overflow area, which is why a wide shadow near the edge of a scroll container can end up clipped rather than scrolled to.
How many shadows can one element have?
The specification sets no limit, and this tool holds ten, which is more than any real design needs. Every layer is another blurred paint, so a list of twelve on a long list of cards is a performance decision, not a style decision.
What is the difference between spread and blur?
Blur softens the edge of the shadow without changing the shape it started from. Spread changes the shape itself, before the blur is applied. Growing a shadow with spread keeps a crisp edge; growing it with blur trades size for softness and makes the whole thing fainter.
Can I animate a box-shadow?
Yes, and the spec defines exactly how lists of different lengths interpolate. Whether you should is a separate question: it repaints a blurred area on every frame, so for anything that runs during a scroll or over many elements, put the shadow on a pseudo-element and animate its opacity instead.
Why does my shadow look grey instead of soft?
Usually because it is one layer with a big blur and a mid opacity. That paints a uniform grey halo. Split it: a tight, slightly darker layer for contact and a wide, much fainter layer for ambience. The Elevation presets above are built that way, and the Layered smooth preset takes it further.
Does this tool send my values anywhere?
No. The generator, the preview and the parser all run in your browser. The site is static files with no backend, so there is nowhere for a value to go.
Related tools
Build a CSS border-radius visually: one radius per corner or the elliptical slash syntax for organic blob shapes, with presets, a live preview, the shortest correct shorthand and the corner longhands.
Build linear, radial and conic CSS gradients visually: any angle, shape, size and centre, colour stops with their own position and opacity, repeating variants and an interpolation colour space, with the background declaration ready to copy.
Check a foreground and background pair against the WCAG 2 contrast thresholds, composite transparency the way a browser does, find the nearest passing colour, and build a tint, shade and harmony palette with the contrast of every swatch.
Draw a CSS easing curve by dragging its two control points, compare it against linear in a live preview, start from the keyword and named presets, and copy the cubic-bezier() value or a ready transition line.