Skip to content

feat(dialog): enable nested dialog backdrops via Base UI forceRender on Backdrop #11214

Description

@MurzNN

Feature description

Problem

Base UI Dialog and Alert Dialog do not render Backdrop for nested dialogs by default (nested dialogs docs). This is intentional: child backdrops are skipped so parent dialogs can stay visible behind the top layer.

For many apps, this creates poor UX when dialogs are stacked (e.g. “Make offer” → “Add organization”):

  • Only the root dialog gets a real backdrop (bg-black/10)
  • Nested layers have no backdrop element, so the screen does not progressively darken
  • Workarounds require fragile CSS (:has() selectors, portal ::before pseudo-backdrops) or duplicating DialogContent outside the registry component

Base UI added an escape hatch in mui/base-ui#2037: forceRender on Backdrop, which renders nested backdrops even when nested === true.

The current shadcn Base UI Dialog registry item does not use this prop:

<DialogPrimitive.Backdrop
  data-slot="dialog-overlay"
  className={cn(/* ... */)}
  {...props}
/>

(AlertDialog Backdrop is the same.)

Proposed solution

Enable nested backdrops in the generated components. Options (in order of preference):

Option A — Default forceRender on Backdrop (simplest)

<DialogPrimitive.Backdrop
  forceRender
  data-slot="dialog-overlay"
  className={cn(/* ... */)}
  {...props}
/>

Stacked bg-black/10 overlays naturally produce progressive darkening. Pair with Base UI’s recommended parent-dim CSS using [data-nested-dialog-open] and var(--nested-dialogs) (documented in Base UI nested dialog examples).

Option B — Opt-in via DialogContent prop

For apps that prefer Base UI’s default (no nested backdrop), expose:

function DialogContent({
  overlayForceRender = true, // or false by default for backward compat
  ...
}: DialogPrimitive.Popup.Props & {
  showCloseButton?: boolean
  overlayForceRender?: boolean
}) {
  return (
    <DialogPortal>
      <DialogOverlay forceRender={overlayForceRender} />
      ...
    </DialogPortal>
  )
}

DialogOverlay already spreads {...props}, so consumers can pass forceRender today — but DialogContent hardcodes <DialogOverlay /> without forwarding overlay props, so the prop never reaches Backdrop unless users recompose the portal manually.

Option C — Forward overlay props from DialogContent

function DialogContent({
  overlayProps,
  ...
}: ... & { overlayProps?: DialogPrimitive.Backdrop.Props }) {
  return (
    <DialogPortal>
      <DialogOverlay {...overlayProps} />
      ...
    </DialogPortal>
  )
}

Why shadcn should care

  • Nested dialogs are a documented Base UI pattern; shadcn is the default integration layer for Base UI in Next.js apps
  • Without forceRender, the registry pushes users toward CSS hacks or forking DialogContent
  • The change is one prop, already supported by @base-ui/react, with negligible bundle impact (+21B gzip per Base UI’s own measurement in [dialog][alert dialog] Add forceRender prop to Backdrop part mui/base-ui#2037
  • Same applies to Alert Dialog Backdrop

References

Workaround today

Apps can wrap the registry component in a local module that re-exports everything except DialogOverlay / DialogContent, adding forceRender there — but this duplicates DialogContent markup and breaks on every shadcn add dialog --overwrite style update.

Affected component/components

Dialog

Additional Context

No response

Before submitting

  • I've made research efforts and searched the documentation
  • I've searched for existing issues and PRs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions