Animating ::backdrop: Fades and Blurs Behind Dialogs and Popovers

The ::backdrop pseudo-element sits directly beneath any element in the top layer — a modal <dialog>, an open popover, an element in fullscreen — and covers the whole viewport. For modal dialogs it is the visual signal that the rest of the page is unavailable. By default it is a faint translucent layer that appears and disappears instantly, which makes even a carefully animated dialog feel abrupt, because the page behind it jumps from bright to dim in one frame. This page shows how to fade and blur the backdrop in both directions, how to theme it with custom properties, and how to keep blur from costing frames. It belongs to Dialog & Popover Animations in the CSS-Only Micro-Interactions & Animations guide.

Why the backdrop needs its own animation

The backdrop is not a child of the dialog, and it is not part of the page. It is rendered as a separate box in the top layer, immediately below its element. That has three consequences:

  1. It does not inherit the dialog's opacity or transforms. Fading the dialog does not fade the backdrop.
  2. It only exists while its element is in the top layer. The instant a dialog closes, the backdrop is removed — unless the dialog's removal from the top layer is deferred.
  3. It is styled only through the ::backdrop selector, so it needs its own transition and its own @starting-style.

Handled well, the backdrop is half of what makes a modal feel smooth: the page dims gradually as the dialog arrives and brightens gradually as it leaves, and the two motions together read as a single event.

Where ::backdrop sits Three stacked layers drawn in perspective. The page is at the bottom. The backdrop, a full-viewport translucent layer, sits above the page. The dialog sits on top. The dialog and backdrop are both in the top layer; the backdrop is not a child of the dialog. Top layer: element above, backdrop below it page content ::backdrop (full viewport) dialog The backdrop is a separate box: it needs its own transition and @starting-style.

The complete implementation

The dialog below fades and scales in while its backdrop fades from transparent to a dim, blurred layer. On close, both reverse, and the dialog's overlay transition keeps the backdrop alive until the exit finishes.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Animated backdrop</title>
<style>
  body { font: 16px/1.5 system-ui, sans-serif; margin: 2rem; }

  dialog {
    /* Theme tokens for the backdrop, set on the dialog so ::backdrop
       inherits them. */
    --scrim: rgb(15 23 42 / 0.5);
    --scrim-blur: 4px;

    border: 0;
    border-radius: 14px;
    padding: 1.5rem;
    opacity: 0;
    transition:
      opacity 180ms ease-in,
      display 180ms allow-discrete,
      overlay 180ms allow-discrete;       /* keeps the backdrop alive on close */
  }

  dialog[open] {
    opacity: 1;
    transition:
      opacity 240ms ease-out,
      display 240ms allow-discrete,
      overlay 240ms allow-discrete;
  }

  @starting-style {
    dialog[open] { opacity: 0; }
  }

  /* The backdrop: closed values and its exit transition. */
  dialog::backdrop {
    background-color: rgb(15 23 42 / 0);
    backdrop-filter: blur(0);
    transition:
      background-color 180ms ease-in,
      backdrop-filter 180ms ease-in,
      display 180ms allow-discrete,
      overlay 180ms allow-discrete;
  }

  /* Open backdrop and its entry transition. */
  dialog[open]::backdrop {
    background-color: var(--scrim, rgb(15 23 42 / 0.5));
    backdrop-filter: blur(var(--scrim-blur, 4px));
    transition:
      background-color 240ms ease-out,
      backdrop-filter 240ms ease-out,
      display 240ms allow-discrete,
      overlay 240ms allow-discrete;
  }

  @starting-style {
    dialog[open]::backdrop {
      background-color: rgb(15 23 42 / 0);
      backdrop-filter: blur(0);
    }
  }

  /* Dark theme: a deeper scrim reads better on dark pages. */
  :root[data-theme="dark"] dialog { --scrim: rgb(0 0 0 / 0.6); }

  @media (prefers-reduced-motion: reduce) {
    dialog::backdrop, dialog[open]::backdrop { transition-duration: 1ms; }
  }
</style>
</head>
<body>
  <button type="button" onclick="document.getElementById('d').showModal()">Open settings</button>
  <dialog id="d" aria-labelledby="d-title">
    <h2 id="d-title">Settings</h2>
    <form method="dialog"><button>Close</button></form>
  </dialog>
</body>
</html>

The demo frames here run without script, so the demo applies the same backdrop recipe to a popover opened by a popovertarget button. A dim scrim behind a popover is not something to ship — the variation below explains why — but it makes the fade and blur easy to see.

Live demoBackdrop that fades and blurs in both directions
Open the panel: the page behind dims and softens over 240ms; close it and the backdrop clears over 180ms instead of vanishing.

The backdrop's closed state is transparent rather than absent, so there is something to transition from and to. The var() calls carry fallbacks because older engines did not let ::backdrop inherit from its element, in which case the custom properties would be undefined there.

The key technique: animating colour, not opacity

It is tempting to animate the backdrop's opacity, but animating its background-color alpha is usually better. The difference shows up once a blur is involved.

Opacity versus colour alpha on a blurred backdrop Left, animating opacity makes the blurred copy of the page cross-fade over the sharp page, which can look like a double image. Right, animating the colour alpha and blur radius separately keeps a single image that smoothly softens and dims. Midway through the entry, two approaches opacity: 0 → 1 sharp and blurred ghosting alpha + blur radius one image softening Animate the scrim colour's alpha; animate or fix the blur radius separately.

When opacity animates on a backdrop with backdrop-filter: blur(), the browser composites a fully blurred copy of the page at partial opacity over the sharp page. Midway, both images are visible, which reads as ghosting. Animating the colour's alpha channel and the blur radius independently keeps one image that progressively dims and softens.

Theming the scrim

A backdrop colour that works on a light page is often wrong on a dark one. A 50% slate scrim over a white page produces a clear, calm grey; the same scrim over a near-black page barely changes it, so the modal loses its separation. Darker themes want a deeper scrim, and some designs use a slightly lighter, tinted scrim over dark pages instead, so the dialog appears to sit in a pool of light.

Because ::backdrop inherits from its element in current engines, the scrim is best expressed as tokens on the dialog, as in the example, and switched by the theme. That keeps the backdrop in step with every other themed surface and avoids a separate dark-mode rule for each dialog. For high-contrast needs, @media (prefers-contrast: more) can raise the scrim's alpha so the boundary between the modal and the inactive page is unmistakable, and forced-colors: active users will see the system's own rendering, since backdrop colours are overridden in forced-colours mode.

Performance: blur is the expensive part

backdrop-filter requires the browser to read back and filter everything behind the element. For a full-viewport backdrop that is the whole screen. A static blur is usually fine on modern hardware because it only needs recomputing when the content beneath changes. Animating the blur radius, as in the example, recomputes the filter on every frame of the transition, which on low-end phones can drop frames noticeably.

Three mitigations, in order of preference:

  • Blur statically, fade the colour. Set the blur on the open backdrop without transitioning it, and animate only the scrim's alpha. The blur appears at once but under a transparent scrim, so it is barely noticeable, then the dimming fades in.
  • Reduce the radius. A 4-pixel blur is much cheaper than 20 pixels and still signals depth.
  • Skip blur on small or low-power contexts. A @media (max-width: 40rem) or @media (prefers-reduced-transparency: reduce) query can drop the filter entirely; the dim scrim alone is enough to show the page is inactive.

Profile with the Performance panel while opening the dialog on a throttled CPU; long Paint or Composite Layers entries during the transition point at the blur. The general technique is in Profiling Animations in DevTools.

Variation: a transparent backdrop for popovers

Popovers also get a ::backdrop when shown, but a dimmed one sends the wrong message: popovers are non-modal, so the page remains interactive and should look it. The backdrop is still useful as a hook for a very light treatment, or can be left fully transparent.

[popover]::backdrop {
  background: transparent;
}

/* An optional hint of focus for a large, content-heavy popover. */
[popover].panel:popover-open::backdrop {
  background: rgb(15 23 42 / 0.06);
  transition: background-color 200ms ease-out, display 200ms allow-discrete, overlay 200ms allow-discrete;
}

@starting-style {
  [popover].panel:popover-open::backdrop { background: rgb(15 23 42 / 0); }
}

For light-dismiss popovers, clicks on the backdrop area reach the page beneath only after closing the popover, which is the platform's light-dismiss behaviour. The styling of the popover itself is covered in Popover Attribute and CSS Styling, and the dialog's own entry in Animating dialog Open and Close.

Browser support

::backdrop is supported in all current engines for modal dialogs, popovers and fullscreen elements. Transitions on it with @starting-style require Chrome and Edge 117+, Firefox 129+ or Safari 17.5+, and transition-behavior: allow-discrete requires Chrome and Edge 117+, Firefox 129+ or Safari 17.4+. The overlay property is Chromium-only at present; elsewhere the backdrop disappears at the start of the close while the dialog still fades. backdrop-filter is supported unprefixed in current versions of all three engines.

FAQ

Why does my ::backdrop disappear instantly when the dialog closes? The backdrop exists only while its element is in the top layer. When the dialog closes, it leaves the top layer and the backdrop goes with it. Transition the dialog's overlay property with allow-discrete so the dialog, and therefore the backdrop, stay in the top layer until the exit finishes, and give the backdrop its own transition.

Can ::backdrop use custom properties from the page? In current engines ::backdrop inherits from its originating element, so custom properties set on the dialog or popover are available inside it. Older engines had the backdrop inherit from nothing, so declare fallback values in the var() calls if you support them.

Is backdrop-filter: blur() expensive? It can be. The browser must blur everything behind the backdrop on every frame that something beneath it or the blur radius changes. Animating the blur radius is especially costly on low-end devices. Prefer a static blur with an animated opacity, or skip blur on small screens.

Should a non-modal popover have a backdrop? Popovers do get a ::backdrop when shown, but a visible one suggests the page is blocked, which is misleading for a non-modal popover the user can click past. Keep popover backdrops transparent or extremely subtle, and reserve dimming for modal dialogs.

Related articles

More pages in the same section.