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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions