Stack CSS box shadows with any colour notation, and never write a declaration the browser will drop: a layer that will not parse is left out and flagged rather than taking every other layer down with it
Initializing in your browser…
Design a CSS button and see every state measured against WCAG on the page colour you name: resting, hover, active, focus and disabled, plus the focus ring and the border.
Generate fake data that is the thing it is labelled: card numbers with a real Luhn check digit, IBANs that pass ISO 7064 MOD 97-10, UUID v4 and v7 per RFC 9562, reserved e-mail and IP ranges, a seed so the same data comes back, and JSON, NDJSON, CSV or SQL output
Build a CSS gradient and choose the colour space it is interpolated in, with the colour it keeps through the middle measured and shown.
The same shadow written with five different colour notations, each put past the browser's own CSS parser. The verdicts below are what Chrome returned on 2026-09-04.
Typed into the layer colour field
#000000, #f00, red, rgb(255 0 0), #3b82f680
What each one produced, before and after this pass
typed before browser after browser #000000 rgba(0, 0, 0, 0.5) accepts rgb(0 0 0 / 0.5) accepts #f00 rgba(240, 0, NaN, 0.5) DROPS rgb(255 0 0 / 0.5) accepts red rgba(237, NaN, NaN, 0.5) DROPS rgb(255 0 0 / 0.5) accepts rgb(255 0 0) rgba(NaN, NaN, 85, 0.5) DROPS rgb(255 0 0 / 0.5) accepts #3b82f680 rgba(59, 130, 246, 0.5) accepts rgb(59 130 246 / 0.251) accepts "DROPS" means the browser threw away the WHOLE declaration, every layer of it, silently. The last row is not a failure but a loss: the eight digit hex had its own alpha, and it was discarded. It is now multiplied by the opacity instead.
A box-shadow value is a comma separated list, and CSS treats a list as valid or invalid in one piece. One NaN anywhere in it removes every layer, with no error and nothing on the page to say why, which is exactly the failure a preview cannot show you. Colour is now read with a full CSS colour parser, an unreadable value is refused by name and that one layer is left out so the others survive, and every value arriving from a shared link is range checked the same way.
Stack as many shadow layers as you like, in any colour notation, and watch the declaration build up. Anything the browser would refuse is caught here first, because a box-shadow list is valid or invalid as a whole: one bad layer takes all of them down, silently, and a preview that never renders it will not tell you.
The thing that decides whether a generated box-shadow works is not how it looks in a preview. It is whether the browser's parser accepts it, and a `box-shadow` value is a comma separated LIST, which CSS treats as valid or invalid in one piece. One bad component anywhere in it makes the browser throw away every layer, in silence, with nothing on the page to say why.
That used to be easy to trigger here. The colour was read with `parseInt(hex.slice(1, 3), 16)` and friends, which works for a six digit hex and produces nonsense for everything else. Measured against the browser's own parser on 2026-09-04, four of seven colour forms a person might reasonably type produced a declaration Chrome threw away: `#f00` became `rgba(240, 0, NaN, 0.5)`, `#FFF` became `rgba(255, 15, NaN, 0.5)`, `red` became `rgba(237, NaN, NaN, 0.5)`, and `rgb(255 0 0)` became `rgba(NaN, NaN, 85, 0.5)`. Each of those silently removed the shadow from the preview and from anything the CSS was pasted into. The shareable link was the same story: three of five URL values produced `NaNpx` or a NaN colour and the same silent disappearance.
Colour now goes through a full CSS colour parser, so three and six and eight digit hex, the 148 colour names, `rgb()`, `hsl()`, `hwb()`, `lab()`, `oklch()` and the rest all work. An eight digit hex keeps its own alpha and the opacity slider MULTIPLIES it rather than replacing it, so `#3b82f680` at 50 percent is 25 percent rather than silently becoming 50. Anything the parser cannot read is refused by name, the field is marked, and that ONE layer is left out of the declaration so the others survive: the panel says how many were excluded and why. Every value from the URL is range checked the same way, so a shared link cannot produce a declaration that will not parse.
The other thing worth knowing about this property is that the order of the layers is load bearing. CSS Backgrounds 3 paints the first shadow in the list on top of the ones after it, which is why the Layered preset stacks five shadows at doubling offset and blur, each at only 5 percent: a real shadow falls off smoothly and a single one cannot. The eye toggle takes a layer out of the output without deleting it, so a layer can be compared in and out, and every preset carries a sentence saying what it is doing and why.
One thing that was checked and found NOT to be a fault: the tool builds its declaration in one place and every preset thumbnail reads that same function, so the thumbnails and the code block cannot disagree. Twelve presets were put past the browser parser and all twelve were accepted, and each thumbnail's computed `box-shadow` was compared with the declaration the tool wrote and matched exactly.
Apply the Medium preset: two layers, 0 4px 6px -1px and 0 2px 4px -2px in black at 10 percent each. The negative spread keeps the shadow from bleeding sideways, giving a clean card lift.
Apply the Neon preset to stack three zero-offset green shadows at blur 10, 30 and 60px with opacity 80, 50 and 30 percent, producing a layered glow that a single shadow cannot replicate.
Type `red` or `#f00` into a layer's colour field. Both are read correctly now; before this pass either one produced a NaN component and the browser dropped every layer of the shadow.
Stack three to five faint layers with growing offset and blur to get depth that reads as real rather than as a single grey blob.
Build the resting and hovered shadows side by side and copy both, using the eye toggle to compare them without losing either.
A long, wide, low-opacity shadow lifts a dialog clear of the page; the Floating and Soft presets are the two shapes that usually work.
Paste your colour into a layer. If the browser would refuse it, the panel says so by name instead of leaving you with an element that has no shadow and no error.
Almost certainly because the colour produced an invalid value and the browser dropped the whole declaration. A box-shadow list is valid or invalid in one piece, so one bad component removes every layer. This tool refuses the bad value by name instead, and keeps the rest of the list.
Yes, and it is the main reason to use this. Real shadows fall off smoothly, which a single shadow cannot do; the Layered preset stacks five at 5 percent each to show what that looks like.
The first one in the list. CSS Backgrounds 3 paints them front to back in the order written, so the move buttons decide which wins where two overlap.
One painted inside the border box rather than outside it, which reads as a recess or a pressed state. The Pressed preset combines a dark inset shadow at the top with a faint white one at the bottom, which is where light would fall on a dented surface.
No. They are painted outside the layout entirely, so they never change an element's size or push anything else around. They also do not respond to pointer events.
Yes. An eight digit hex or an rgb() with an alpha keeps that alpha, and the opacity slider multiplies it, so `#3b82f680` at 50 percent comes out at 25 percent rather than silently losing the alpha it was given.
This runs as client-side JavaScript. Keys, tokens, payloads, and other inputs never leave your device.