Free · Fast · Privacy-first

CSS Multiple Box Shadows

CSS allows any number of box-shadow layers on a single element, each with its own offset, blur, spread, colour, and optional inset keyword.

Add multiple shadow layers with individual controls

🔒

Live preview showing all layers composited together

Supports mixing inset and outer shadows in one declaration

Generates the full comma-separated CSS declaration

Cost
Free forever
Sign-up
Not required
Processing
In your browser
Privacy
Files stay local
FreeNo signupWhite-label

Add this Shadow Effect Builder to your website

Drop the Shadow Effect Builder into any page — blog post, product docs, intranet, school portal — with a single line of HTML. Your visitors get the full tool, processed entirely in their browser. No backend, no uploads, no signup.

  • Files stay 100% in the visitor's browser
  • Responsive — adapts to any container width
  • Free forever, no API key needed

Embed code

<iframe
  src="https://www.fixtools.io/css-tool/shadow-effect-builder?embed=1"
  width="100%"
  height="780"
  frameborder="0"
  style="border:0;border-radius:16px;max-width:900px;"
  title="Shadow Effect Builder by FixTools"
  loading="lazy"
  allow="clipboard-write"
></iframe>

Attribution-friendly: a small "Powered by FixTools" link appears in the embed footer.

How Multiple box-shadow Layers Stack and When to Use Each Pattern

CSS processes the box-shadow property as a comma-separated list of shadow declarations. The browser renders them in order, with the first listed shadow appearing on top visually. Each shadow in the list operates independently with its own offset, blur, spread, colour, and optional inset keyword. They are composited on top of each other and on top of the element's background. This means each layer adds its visual contribution independently. A dark shadow and a light shadow on the same element both appear, each at their specified opacity and position, without blending or merging.

Three practical multi-layer patterns appear regularly in production. The two-component elevation shadow (used by Material Design) combines a close contact shadow at low blur with a diffused ambient shadow at higher blur and lower opacity. For example: box-shadow: 0 2px 4px rgba(0,0,0,0.18), 0 6px 16px rgba(0,0,0,0.1). The first layer creates the tight directional shadow; the second creates the wider haze. Together they match how physical objects cast shadows more accurately than a single layer can. The second common pattern adds a focus ring without a separate outline: box-shadow: 0 2px 8px rgba(0,0,0,0.12), 0 0 0 3px rgba(59,130,246,0.5). The second shadow uses zero offset and a spread of 3px to create a solid ring at the desired focus colour.

Performance consideration: each shadow layer adds compositing work during rendering. Two or three layers on standard card and button components render without any measurable performance cost in modern browsers because the layers are rasterised and composited on the GPU once. Stacking ten or more layers on a single element, or applying multi-layer shadow definitions to every item in a long scrollable list, can introduce real frame rate issues on low-powered devices because each scroll frame requires re-composition. Keep shadow layers at three or fewer on any element that appears more than a dozen times per page, and test scroll performance on a mid-range Android device if shadow complexity is a concern. The visual return on adding a fourth or fifth layer is typically minimal anyway.

Multi-layer shadows pair particularly well with CSS custom properties because the comma-separated nature of the value lends itself to composition. You can define each shadow concern as its own property, such as --shadow-elevation for the standard depth cue and --shadow-focus for the focus ring, then compose them together at the use site with box-shadow: var(--shadow-elevation), var(--shadow-focus). Updating one concept in one place updates every consumer simultaneously, and individual concerns can be toggled on or off without rewriting the full declaration. This composition pattern keeps complex multi-layer shadows maintainable in large design systems where dozens of components share elevation and focus styling, and where ad-hoc value copying would quickly drift out of sync across the codebase.

How to use this tool

💡

Add a first layer with a small offset and tight blur for the contact shadow. Click Add Layer and increase the offset and blur with lower opacity for the ambient shadow. Add a third inset layer for an inner highlight if needed. Copy the combined CSS declaration.

How It Works

Step-by-step guide to css multiple box shadows:

  1. 1

    Add the first shadow layer

    Set a close, directional shadow for the first layer of the stack: a small offset of 1 to 2 pixels, a moderate blur of 4 to 6 pixels, and a medium opacity dark colour around rgba(0,0,0,0.15). This first layer creates the tight contact shadow that anchors the element to the surface beneath it. Tune this layer to read well on its own before adding any additional layers on top of it.

  2. 2

    Add a second ambient layer

    Click the Add Layer control to introduce a second shadow with a larger offset, a higher blur radius of roughly 12 to 24 pixels, and a noticeably lower opacity around 0.08 to 0.1. This wider ambient layer produces the diffused outer haze that mimics how light scatters around a real object. The combination of the close and ambient layers reads as a single coherent depth cue rather than two separate effects, which is the entire point of the multi-layer technique.

  3. 3

    Optionally add an inset or focus ring layer

    Add a third layer either as an inset highlight at the top edge of the element using inset 0 1px 0 rgba(255,255,255,0.1) for a gloss treatment, or as a zero-offset, zero-blur, spread-only layer for a focus ring effect. The third layer is optional but unlocks rich finishes that two layers alone cannot achieve. Toggle the layer on and off in the preview to confirm whether it improves the composition or simply adds visual noise without benefit.

  4. 4

    Copy the full comma-separated declaration

    Click Copy CSS to copy the complete box-shadow value containing every active layer in the correct order. Paste the declaration into your component CSS rule as a single box-shadow property value. If you store the value in a CSS custom property for reuse, paste the entire comma-separated list as the variable value and reference it from each consuming component with var() for ongoing maintainability.

Real-world examples

Common situations where this approach makes a real difference:

Clickable card with elevation and accessible focus ring

A developer builds a clickable card component that needs both an elevation shadow at rest and a visible focus ring for keyboard navigation users. Using two box-shadow layers, the first handles the resting elevation with a soft 0 2px 8px rgba(0,0,0,0.1) and the second provides the focus ring at 0 0 0 3px rgba(59,130,246,0.6) inside the :focus-visible rule. One property declaration handles both concerns without requiring a separate outline style that would conflict with the rounded border-radius of the card. The focus ring layer also animates in cleanly because both states keep the same number of layers.

Modal dialog with two-component depth

A designer implements a confirmation modal using a two-layer shadow defined explicitly in CSS: 0 8px 20px rgba(0,0,0,0.2) for the close directional shadow that anchors the modal in space, and 0 24px 60px rgba(0,0,0,0.12) for the wide ambient haze that establishes the surrounding atmosphere. The combined shadow makes the modal feel genuinely elevated above the dimmed background rather than sitting flat on top of it like a sticker. The paired layers also read as more polished than any single-layer alternative the team had previously tested in early design exploration sessions before settling on this final pattern.

Premium CTA button with gloss finish

A developer adds three carefully tuned shadow layers to a premium CTA button on a high-end product landing page: an outer elevation shadow that lifts the button off the background, a zero-offset spread border that creates a crisp ring around the button edge, and an inset white highlight at the top interior edge that simulates light reflecting off the button surface. The triple-layer combination gives the button a refined gloss finish appearance that no single layer could achieve. All three layers are defined as separate CSS custom properties so the design team can update each visual aspect of the button independently without rewriting the full shadow declaration.

Design system elevation scale via custom properties

A design systems engineer defines four explicit elevation levels as multi-layer custom properties: --elevation-1 through --elevation-4, each containing a two-layer shadow declaration tuned for that tier. All card, button, modal, drawer, and popover components reference these named variables rather than embedding raw shadow values, which means the elevation scale stays internally consistent across the entire component library. When the design team adjusts the elevation scale during a visual refresh, a single update to the custom property definitions at the root level propagates to every component instance simultaneously, dramatically reducing the manual coordination work required to ship a system-wide elevation change.

When to use this guide

Use this when a single box-shadow layer is not producing the depth or effect you need, or when you want to combine an elevation shadow with a focus ring or inner highlight in one CSS property.

Pro tips

Get better results with these expert suggestions:

1

Simulate a border with a zero-blur spread layer

A zero-offset, zero-blur shadow with only a spread value creates a solid ring around the element that looks identical in appearance to a border, without taking up any space in the box model or affecting computed layout dimensions. Combining box-shadow: 0 4px 12px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.06) adds a subtle hairline border appearance alongside an elevation shadow in a single CSS property. The technique is especially useful when the real border property is already reserved for another purpose such as state styling.

2

Animate the focus ring layer independently with CSS variables

Separate your focus ring shadow into a dedicated CSS custom property: --focus-ring: 0 0 0 3px rgba(59,130,246,0.5). Set the default value to 0 0 0 0 transparent so the ring is effectively invisible at rest, then override --focus-ring to the full value inside the :focus-visible rule. This pattern animates only the focus ring layer without disrupting the resting elevation shadow at all, because both layers continue to exist throughout the transition and only their internal values change as the state shifts.

3

Use a third shadow layer for inner content highlight

After an outer elevation shadow and an ambient layer, a subtle inset shadow at the top of the element simulates a highlight gloss as if light were reflecting off the upper edge of the surface. box-shadow: [outer1], [outer2], inset 0 1px 0 rgba(255,255,255,0.1) adds a barely visible white pixel line at the top interior, giving a slight glossy finish that reads particularly well on dark buttons, dark cards, and any saturated coloured surface where the white highlight contrasts cleanly with the background fill colour underneath.

4

Document each layer's purpose in a source comment

Multi-layer shadow declarations are not self-documenting because the comma-separated value gives no hint as to what each layer is intended to accomplish. In your CSS source file, add an inline comment identifying each layer in order: /* elevation, ambient, focus ring */ on the same line as the box-shadow declaration. Future contributors who modify the component will immediately know which layer to change without reverse-engineering the values from the rendered output. The same convention applies inside CSS custom property definitions for shared shadow tokens used by the system.

5

Put the tightest shadow first in the list

CSS renders shadows from the first listed item downward. The tightest, most specific shadow (low blur, close to the element) should be first so it appears on top. Wider ambient shadows go in subsequent layers.

6

Use a zero-offset spread layer as a focus ring

box-shadow: [existing-shadow], 0 0 0 3px rgba(59,130,246,0.5) adds a focus ring using only the box-shadow property. This avoids conflicting with CSS outline styles while keeping focus visible and styled to match your design.

7

Define shadow layers as separate CSS custom properties

When using a CSS custom property for shadows, define separate variables for the elevation shadow and the highlight layer. This makes it easy to change one aspect of a complex multi-layer shadow without reconstructing the full declaration.

FAQ

Frequently asked questions

CSS imposes no formal limit on the number of box-shadow layers in a single declaration, so in principle you can stack hundreds, which is what the long-shadow technique relies on. In practice, one to three layers covers every common production use case for cards, buttons, and modals. More than three layers rarely produces a visible improvement to the rendered result and adds browser compositing work that becomes measurable on lower-end devices. The most common multi-layer patterns in production design systems use two elevation layers plus an optional focus ring or inner highlight as the third. Anything beyond that is almost always either decorative experimentation or a long-shadow effect rather than a standard elevation treatment.
Yes, layer order has a meaningful effect on the rendered result. The first layer in the comma-separated list renders on top visually, with subsequent layers stacking behind it in the order written. For paired elevation shadows, put the tighter, closer shadow first and the wider ambient shadow second so the close shadow remains crisply visible above the diffused outer layer. For focus rings, put the focus ring layer last so it is not obscured by other shadows above it on the same element. Inset layers render on a separate plane from outer shadows, so they can sit at any position relative to outer layers in the list without conflicting visually with the surrounding stack.
Yes, and this is a very common pattern in production design systems for buttons and inputs. Each layer in the comma-separated list is independent and the inset keyword applies only to the layer where it is written. For example, box-shadow: 0 4px 12px rgba(0,0,0,0.15), inset 0 1px 3px rgba(0,0,0,0.1) combines an outer elevation shadow with an inner detail shadow in a single declaration without any conflict. The browser renders both layers independently, with the outer shadow extending outside the element bounds and the inset shadow living inside them. Mixing inset and outer layers does not cause any unexpected interaction or rendering issues.
Define both the resting and hover multi-layer shadow declarations explicitly in their full form, then use a transition such as transition: box-shadow 0.2s ease to animate between them. The browser interpolates each corresponding layer pair in parallel, so the entire stack animates smoothly. If the number of layers differs between states, for example when adding a focus ring layer on focus, pre-define placeholder layers at zero opacity in both states to keep the layer counts matched and avoid interpolation artifacts during the transition. The browser cannot interpolate between declarations with different layer counts and will fall back to a hard cut-over, which usually looks broken rather than animated.
Yes, append a zero-offset, zero-blur, spread-only layer to your existing shadow declaration: box-shadow: [existing-shadow], 0 0 0 3px rgba(59,130,246,0.5). The spread-only layer creates a solid ring that does not interfere with the elevation shadow at all, because the two layers occupy different visual zones around the element. This is the recommended approach for custom focus rings in design systems that need the focus indicator styled to exactly match the brand colour rather than relying on the browser default outline. The technique works particularly well alongside the outline-offset property when you want to nudge the focus ring slightly away from the element edge.
Two to three layers have negligible performance impact in modern browsers, because layers are composited on the GPU once and the rasterised result is cached for reuse during scroll and idle paint cycles. Performance becomes a real concern when combining many layers on many elements that animate simultaneously, particularly during scroll-driven animations or long lists with hover-animated items. If you have a long scrollable list where each item has a three-layer animated shadow, test scrolling on a mid-range Android device to confirm acceptable frame rates. Limit shadow animation to genuinely interactive elements rather than applying animated multi-layer shadows uniformly to every list item visible at once.
Yes, and this is the recommended approach for any design system with a meaningful elevation scale. Defining each shadow concept as a separate custom property and composing them inside the box-shadow declaration makes complex shadows maintainable across many components. For example: --shadow-elevation: 0 4px 12px rgba(0,0,0,0.12); --shadow-focus: 0 0 0 3px rgba(59,130,246,0.5). Then at the use site: box-shadow: var(--shadow-elevation). In the focus state: box-shadow: var(--shadow-elevation), var(--shadow-focus). One update to either variable propagates to every consuming component immediately, supporting both theme switching and design refresh work over time.
For simple elevation needs, a single well-tuned shadow is often sufficient and easier to maintain across a small project. The two-layer approach is worth the added complexity when you want the shadow to read as more natural and physically accurate, when you need to combine elevation with a focus ring in one property to avoid conflicts with the separate outline property, or when you are matching a design system that already uses paired shadows such as Material Design. With custom property composition, multi-layer shadows are no harder to maintain in practice than single-layer ones in a large codebase, so the simplicity argument loses force once you adopt CSS variables for shadow tokens.
Muddy or grey results usually come from stacking layers at high opacity without considering how the colours combine. Each shadow layer adds its opacity-weighted contribution on top of the previous one, so two 30 percent opacity dark shadows in the same place stack to roughly 51 percent opacity total, which is heavier than most designs want. The fix is to keep total opacity across all layers below roughly 30 to 35 percent for elevation shadows by using low individual layer opacities such as 0.08 to 0.18 each. Preview the rendered output on the actual background colour rather than pure white, because the muddy effect is much more visible against off-white or neutral surfaces.
Most modern design tools including Figma, Sketch, and Adobe XD export multi-shadow declarations in a format compatible with CSS box-shadow. Figma in particular generates comma-separated multi-layer values that paste directly into CSS without modification. The main caveat is that some tools represent shadow opacity slightly differently from the rendered browser result, so the imported values may need 5 to 10 percent opacity adjustment when previewed in the browser. Always paste the exported values into a live preview environment and adjust against the real page background rather than trusting the design tool preview, which often uses a default white canvas that does not match production conditions accurately.

Related guides

More use-case guides for the same tool:

Ready to get started?

Open the full Shadow Effect Builder — free, no account needed, works on any device.

Open Shadow Effect Builder →

Free · No account needed · Works on any device