` element which will replace the default backdrop.
* The backdrop should have `aria-hidden="true"`.
*
+ * Accepts an `appearance` prop to control backdrop visibility:
+ * - `'dimmed'`: Always shows a dimmed backdrop, regardless of nesting.
+ * - `'transparent'`: Always shows a transparent backdrop.
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
*/
- backdrop?: Slot<'div'>;
+ backdrop?: Slot
;
root: Slot<'div'>;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
@@ -44,7 +66,12 @@ export type DialogSurfaceState = ComponentState &
Pick & {
open?: boolean;
unmountOnClose?: boolean;
-
+ /**
+ * Whether the backdrop should be treated as nested (transparent).
+ * When inside an OverlayDrawer, this is `false` even though `isNestedDialog` may be `true`,
+ * preventing the false-positive transparent backdrop.
+ */
+ treatBackdropAsNested: boolean;
/**
* Transition status for animation.
* In test environment, this is always `undefined`.
@@ -52,4 +79,5 @@ export type DialogSurfaceState = ComponentState &
* @deprecated Will be always `undefined`.
*/
transitionStatus?: 'entering' | 'entered' | 'idle' | 'exiting' | 'exited' | 'unmounted';
+ backdropAppearance?: DialogBackdropSlotProps['appearance'];
};
diff --git a/packages/react-components/react-dialog/library/src/components/DialogSurface/index.ts b/packages/react-components/react-dialog/library/src/components/DialogSurface/index.ts
index 6de98e07cf806..78aec16f608ca 100644
--- a/packages/react-components/react-dialog/library/src/components/DialogSurface/index.ts
+++ b/packages/react-components/react-dialog/library/src/components/DialogSurface/index.ts
@@ -1,5 +1,6 @@
export { DialogSurface } from './DialogSurface';
export type {
+ DialogBackdropSlotProps,
DialogSurfaceContextValues,
DialogSurfaceElement,
DialogSurfaceProps,
diff --git a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts
index df05129c64297..c8b6892de25ce 100644
--- a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts
+++ b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts
@@ -12,7 +12,7 @@ import {
} from '@fluentui/react-utilities';
import * as React from 'react';
-import { useDialogContext_unstable } from '../../contexts';
+import { useDialogContext_unstable, useDialogBackdropContext_unstable } from '../../contexts';
import { useDisableBodyScroll } from '../../utils/useDisableBodyScroll';
import { DialogBackdropMotion } from '../DialogBackdropMotion';
import { useMotionForwardedRef } from '../MotionRefForwarder';
@@ -35,6 +35,8 @@ export const useDialogSurface_unstable = (
const modalType = useDialogContext_unstable(ctx => ctx.modalType);
const isNestedDialog = useDialogContext_unstable(ctx => ctx.isNestedDialog);
+ const backdropOverride = useDialogBackdropContext_unstable();
+ const treatBackdropAsNested = backdropOverride ?? isNestedDialog;
const modalAttributes = useDialogContext_unstable(ctx => ctx.modalAttributes);
const dialogRef = useDialogContext_unstable(ctx => ctx.dialogRef);
@@ -79,8 +81,12 @@ export const useDialogSurface_unstable = (
elementType: 'div',
});
+ const backdropAppearance = backdrop?.appearance;
+
if (backdrop) {
backdrop.onClick = handledBackdropClick;
+ // remove backdrop.appearance so it is not passed to the DOM
+ delete backdrop.appearance;
}
const { disableBodyScroll, enableBodyScroll } = useDisableBodyScroll();
@@ -109,6 +115,8 @@ export const useDialogSurface_unstable = (
open,
backdrop,
isNestedDialog,
+ treatBackdropAsNested,
+ backdropAppearance,
unmountOnClose,
mountNode: props.mountNode,
root: slot.always(
diff --git a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurfaceStyles.styles.ts b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurfaceStyles.styles.ts
index d3fed44c49bd2..c6fac8f9d4eb2 100644
--- a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurfaceStyles.styles.ts
+++ b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurfaceStyles.styles.ts
@@ -1,7 +1,6 @@
'use client';
import { makeResetStyles, makeStyles, mergeClasses } from '@griffel/react';
-import type { SlotClassNames } from '@fluentui/react-utilities';
import { tokens } from '@fluentui/react-theme';
import { createFocusOutlineStyle } from '@fluentui/react-tabster';
import {
@@ -12,6 +11,7 @@ import {
SURFACE_PADDING,
} from '../../contexts';
import type { DialogSurfaceSlots, DialogSurfaceState } from './DialogSurface.types';
+import type { SlotClassNames } from '@fluentui/react-utilities';
export const dialogSurfaceClassNames: SlotClassNames> = {
root: 'fui-DialogSurface',
@@ -82,11 +82,12 @@ const useStyles = makeStyles({
export const useDialogSurfaceStyles_unstable = (state: DialogSurfaceState): DialogSurfaceState => {
'use no memo';
- const { isNestedDialog, root, backdrop, open, unmountOnClose } = state;
+ const { root, backdrop, open, unmountOnClose, treatBackdropAsNested, backdropAppearance } = state;
const rootBaseStyle = useRootBaseStyle();
const backdropBaseStyle = useBackdropBaseStyle();
const styles = useStyles();
+ const isBackdropTransparent = backdropAppearance ? backdropAppearance === 'transparent' : treatBackdropAsNested;
const mountedAndClosed = !unmountOnClose && !open;
@@ -101,8 +102,8 @@ export const useDialogSurfaceStyles_unstable = (state: DialogSurfaceState): Dial
backdrop.className = mergeClasses(
dialogSurfaceClassNames.backdrop,
backdropBaseStyle,
- isNestedDialog && styles.nestedDialogBackdrop,
mountedAndClosed && styles.dialogHidden,
+ isBackdropTransparent && styles.nestedDialogBackdrop,
backdrop.className,
);
}
diff --git a/packages/react-components/react-dialog/library/src/contexts/dialogBackdropContext.ts b/packages/react-components/react-dialog/library/src/contexts/dialogBackdropContext.ts
new file mode 100644
index 0000000000000..f03656f8ab8a7
--- /dev/null
+++ b/packages/react-components/react-dialog/library/src/contexts/dialogBackdropContext.ts
@@ -0,0 +1,13 @@
+'use client';
+
+import * as React from 'react';
+
+export type DialogBackdropContextValue = boolean;
+
+export const DialogBackdropContext = React.createContext(undefined);
+
+export const DialogBackdropProvider = DialogBackdropContext.Provider;
+
+export const useDialogBackdropContext_unstable = (): DialogBackdropContextValue | undefined => {
+ return React.useContext(DialogBackdropContext);
+};
diff --git a/packages/react-components/react-dialog/library/src/contexts/index.ts b/packages/react-components/react-dialog/library/src/contexts/index.ts
index 4fe17b52c0c6d..5adcb18912f63 100644
--- a/packages/react-components/react-dialog/library/src/contexts/index.ts
+++ b/packages/react-components/react-dialog/library/src/contexts/index.ts
@@ -9,4 +9,10 @@ export {
export type { DialogContextValue } from './dialogContext';
export { DialogContext, DialogProvider, useDialogContext_unstable } from './dialogContext';
export type { DialogSurfaceContextValue } from './dialogSurfaceContext';
+export type { DialogBackdropContextValue } from './dialogBackdropContext';
export { DialogSurfaceContext, DialogSurfaceProvider, useDialogSurfaceContext_unstable } from './dialogSurfaceContext';
+export {
+ DialogBackdropContext,
+ DialogBackdropProvider,
+ useDialogBackdropContext_unstable,
+} from './dialogBackdropContext';
diff --git a/packages/react-components/react-dialog/library/src/index.ts b/packages/react-components/react-dialog/library/src/index.ts
index b3f35c11aea04..57466863cc3e5 100644
--- a/packages/react-components/react-dialog/library/src/index.ts
+++ b/packages/react-components/react-dialog/library/src/index.ts
@@ -59,6 +59,7 @@ export {
renderDialogSurface_unstable,
} from './DialogSurface';
export type {
+ DialogBackdropSlotProps,
DialogSurfaceProps,
DialogSurfaceSlots,
DialogSurfaceState,
@@ -80,9 +81,11 @@ export {
useDialogSurfaceContext_unstable,
DialogProvider,
DialogSurfaceProvider,
+ DialogBackdropProvider,
+ useDialogBackdropContext_unstable,
} from './contexts/index';
-export type { DialogContextValue, DialogSurfaceContextValue } from './contexts/index';
+export type { DialogContextValue, DialogSurfaceContextValue, DialogBackdropContextValue } from './contexts/index';
export {
DIALOG_MEDIA_QUERY_BREAKPOINT_SELECTOR,
diff --git a/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.md b/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.md
new file mode 100644
index 0000000000000..e163cfe3ff7c9
--- /dev/null
+++ b/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.md
@@ -0,0 +1,12 @@
+The `backdrop` slot on `DialogSurface` accepts an `appearance` prop that allows you to explicitly control the backdrop appearance of the dialog.
+
+By default, DialogSurface automatically determines the backdrop appearance based on context: standalone dialogs show a dimmed backdrop, while nested dialogs (inside another Dialog) show a transparent backdrop to avoid stacking multiple dimmed layers.
+
+Use `backdrop={{ appearance: "dimmed" }}` when rendering a Dialog inside components that internally use Dialog (like `OverlayDrawer`) but the dialog should visually behave as standalone with a dimmed backdrop.
+
+- **`'dimmed'`**: Always shows a dimmed backdrop, regardless of nesting.
+- **`'transparent'`**: Always shows a transparent backdrop.
+
+```tsx
+
+```
diff --git a/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.stories.tsx b/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.stories.tsx
new file mode 100644
index 0000000000000..1cbe749a9367b
--- /dev/null
+++ b/packages/react-components/react-dialog/stories/src/Dialog/DialogBackdropAppearance.stories.tsx
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import type { JSXElement } from '@fluentui/react-components';
+import {
+ Dialog,
+ DialogTrigger,
+ DialogSurface,
+ DialogTitle,
+ DialogBody,
+ DialogActions,
+ DialogContent,
+ OverlayDrawer,
+ DrawerBody,
+ DrawerHeader,
+ DrawerHeaderTitle,
+ Button,
+ Label,
+ RadioGroup,
+ Radio,
+ useId,
+ tokens,
+ makeStyles,
+} from '@fluentui/react-components';
+import { Dismiss24Regular } from '@fluentui/react-icons';
+import story from './DialogBackdropAppearance.md';
+
+const useStyles = makeStyles({
+ field: {
+ display: 'grid',
+ gridRowGap: tokens.spacingVerticalS,
+ marginBottom: tokens.spacingVerticalL,
+ },
+});
+
+type BackdropAppearanceOption = 'dimmed' | 'transparent';
+
+export const BackdropAppearance = (): JSXElement => {
+ const styles = useStyles();
+ const labelId = useId('backdrop-appearance-label');
+
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
+ const [backdropAppearance, setBackdropAppearance] = React.useState();
+ const backdropProp = backdropAppearance ? { appearance: backdropAppearance } : undefined;
+
+ return (
+ <>
+
+
+ setDrawerOpen(open)}>
+
+ }
+ onClick={() => setDrawerOpen(false)}
+ />
+ }
+ >
+ Drawer
+
+
+
+
+
+
+ setBackdropAppearance(data.value as BackdropAppearanceOption)}
+ aria-labelledby={labelId}
+ >
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+BackdropAppearance.parameters = {
+ docs: {
+ description: {
+ story,
+ },
+ },
+};
diff --git a/packages/react-components/react-dialog/stories/src/Dialog/index.stories.tsx b/packages/react-components/react-dialog/stories/src/Dialog/index.stories.tsx
index defa29b465345..08b5b71bd7ac7 100644
--- a/packages/react-components/react-dialog/stories/src/Dialog/index.stories.tsx
+++ b/packages/react-components/react-dialog/stories/src/Dialog/index.stories.tsx
@@ -8,6 +8,7 @@ import ssrMd from './DialogSSR.md';
export { Default } from './DialogDefault.stories';
export { NonModal } from './DialogNonModal.stories';
export { Alert } from './DialogAlert.stories';
+export { BackdropAppearance } from './DialogBackdropAppearance.stories';
export { ScrollingLongContent } from './DialogScrollingLongContent.stories';
export { KeepRenderedInTheDOM } from './DialogKeepRenderedInTheDOM.stories';
export { Actions } from './DialogActions.stories';
diff --git a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawerSurface/useOverlayDrawerSurfaceStyles.styles.ts b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawerSurface/useOverlayDrawerSurfaceStyles.styles.ts
index 0e8a4befb77d4..b2434d690ea5a 100644
--- a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawerSurface/useOverlayDrawerSurfaceStyles.styles.ts
+++ b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawerSurface/useOverlayDrawerSurfaceStyles.styles.ts
@@ -29,7 +29,7 @@ const useBackdropStyles = makeStyles({
export const useOverlayDrawerSurfaceStyles_unstable = (state: DialogSurfaceState): DialogSurfaceState => {
'use no memo';
- const { isNestedDialog, backdrop, open, unmountOnClose } = state;
+ const { treatBackdropAsNested, backdrop, open, unmountOnClose } = state;
const backdropResetStyles = useBackdropResetStyles();
const backdropStyles = useBackdropStyles();
@@ -39,7 +39,7 @@ export const useOverlayDrawerSurfaceStyles_unstable = (state: DialogSurfaceState
if (backdrop) {
backdrop.className = mergeClasses(
backdropResetStyles,
- isNestedDialog && backdropStyles.nested,
+ treatBackdropAsNested && backdropStyles.nested,
mountedAndClosed && backdropStyles.drawerHidden,
backdrop.className,
);
diff --git a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/renderOverlayDrawer.tsx b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/renderOverlayDrawer.tsx
index 6f1530705c4b2..e7389bb00baa5 100644
--- a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/renderOverlayDrawer.tsx
+++ b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/renderOverlayDrawer.tsx
@@ -3,6 +3,7 @@
import { assertSlots } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import { DrawerContextValue, DrawerProvider } from '../../contexts/drawerContext';
+import { DialogBackdropProvider } from '@fluentui/react-dialog';
import type { OverlayDrawerState, OverlayDrawerInternalSlots } from './OverlayDrawer.types';
@@ -17,9 +18,11 @@ export const renderOverlayDrawer_unstable = (
return (
-
-
-
+
+
+
+
+
);
};