Overlays
Overview
Section titled “Overview”Overlays is not one control but the shared substrate every dismissable surface in this library is
built on: OverlayStack/OverlaySession (per-window z-order, cancelable close, Escape and
outside-press routing), NaviusOverlayLayer (an in-window Grid portal target replacing WPF’s
Popup where window-level input hooks must reach), and NaviusOverlaySurfaceBase (the shared
open/close state machine Dialog, AlertDialog, and Drawer derive from). Popover, PreviewCard, and
Tooltip do not derive from the base class, but push directly onto OverlayStack and register their
Popup-hosted content as an input root, the other consumption pattern this substrate supports.
xmlns:overlay="clr-namespace:Navius.Wpf.Primitives.Controls.OverlaySurface;assembly=Navius.Wpf.Primitives"
<Grid> <!-- app content -->
<!-- one layer per window, stretched over the root; layer-hosted surfaces (Dialog/AlertDialog/Drawer) are declared as its direct XAML children --> <overlay:NaviusOverlayLayer /></Grid>A window needs at most one NaviusOverlayLayer, registered the first time it loads
(ConditionalWeakTable<Window, NaviusOverlayLayer>). A control that hosts its own popup content in
a native Popup (Popover, PreviewCard, Tooltip) does not need a layer at all: it calls
OverlayStack.GetFor(window) directly and calls OverlaySession.RegisterInputRoot(popupContent)
so Escape and outside-press routing also fire inside the Popup’s own HwndSource.
Properties
Section titled “Properties”| Property | Type | Default | From | Description |
|---|---|---|---|---|
Modal |
bool |
false |
OverlayOptions |
Exposed on the session (OverlaySession.IsModal) for a consumer to decide what modality means visually; the stack itself renders no backdrop. |
CloseOnEscape |
bool |
true |
OverlayOptions |
Escape can close this session, subject to OverlayDismissPolicy.FindEscapeTarget’s topmost-first scan. |
CloseOnOutsideClick |
bool |
false |
OverlayOptions |
A press outside the session’s root (or any registered input root) can close it. |
TrapFocus |
bool |
false |
OverlayOptions |
Tab/Shift+Tab cycle within the session root; focus moves to the first focusable descendant on push. |
RestoreFocus |
bool |
true |
OverlayOptions |
Focus returns to the pre-push focused element on pop, if RestoreFocusTarget was captured (only when TrapFocus was true) and still inside the session’s subtree. |
Sessions |
IReadOnlyList<OverlaySession> |
- |
OverlayStack |
Current sessions for a window, bottommost first. |
Topmost |
OverlaySession? |
- |
OverlayStack |
The topmost session, or null. |
Root |
FrameworkElement |
- |
OverlaySession |
The overlay’s root element, as passed to Push. |
StackIndex |
int |
- |
OverlaySession |
Position within the owning stack; 0 is bottommost. |
IsModal / IsClosed |
bool |
- |
OverlaySession |
Reflect Options.Modal / whether RequestClose already succeeded. |
InputRoots |
IReadOnlyList<FrameworkElement> |
- |
OverlaySession |
Extra roots registered via RegisterInputRoot, whose own PreviewKeyDown/PreviewMouseDown participate in this session’s routing. |
Reason |
OverlayCloseReason |
- |
OverlayClosingEventArgs |
Why the close was requested: EscapeKey, OutsidePress, or Programmatic. |
Cancel |
bool |
false |
OverlayClosingEventArgs |
Set true to keep the session open; blocks the subsequent Closed event. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
OverlayStack.GetFor(Window) |
Gets (creating if needed) the per-window stack. |
OverlayStack.Push(FrameworkElement, OverlayOptions) |
Opens a new overlay session on top of the stack; captures RestoreFocusTarget and engages the focus trap when TrapFocus is set. |
OverlaySession.RegisterInputRoot(FrameworkElement) |
Registers an additional popup-tree root (e.g. a Popup’s Child) so Escape and outside-press routing also fire inside its own HwndSource. Safe to call more than once; a no-op after the first. |
OverlaySession.RequestClose(OverlayCloseReason) |
Fires the cancelable Closing event; pops the session and fires Closed unless canceled. Returns whether the session is now closed. |
NaviusOverlayLayer.GetFor(Window) |
The layer registered for a window, or null if none has loaded yet. |
OverlayDismissPolicy.FindEscapeTarget(sessions) |
Pure: topmost-first scan for the first session with CloseOnEscape enabled, skipping sessions above it that opted out. |
OverlayDismissPolicy.FindOutsidePressTarget(sessions, isPressInsideRoot) |
Pure: topmost-first scan for the first session with CloseOnOutsideClick enabled whose root (and every session stacked at or above it) the press falls outside of. |
OverlayDismissPolicy.ShouldRestoreFocus(restoreFocusOption, focusIsWithinOverlaySubtree) |
Pure: true only if RestoreFocus was requested and focus is still somewhere inside the overlay’s subtree at close time. |
Commands
Section titled “Commands”| Command | Description |
|---|---|
NaviusOverlaySurfaceBase.CloseCommand |
Static RoutedCommand; content inside a NaviusOverlaySurfaceBase-derived surface (Dialog, AlertDialog, Drawer) binds to it to request a close. Documented per-family on those pages. |
Events
Section titled “Events”| Event | Signature | Fires when |
|---|---|---|
OverlaySession.Closing |
EventHandler<OverlayClosingEventArgs> |
RequestClose was called; cancelable. |
OverlaySession.Closed |
EventHandler |
A close request succeeded (was not canceled); the session is popped from its stack. |
NaviusOverlaySurfaceBase.Opened / Closing / Closed |
see Dialog/AlertDialog/Drawer pages | Forwarded 1:1 from the underlying OverlaySession once a layer-hosted surface engages. |
Keyboard interactions
Section titled “Keyboard interactions”| Key | Behavior |
|---|---|
Escape |
OverlayStack’s window-level PreviewKeyDown hook (and, for any session with registered input roots, the same hook attached directly to each input root) calls OverlayDismissPolicy.FindEscapeTarget, which scans topmost-first and requests a close on the first session with CloseOnEscape enabled. |
Tab / Shift + Tab |
When a session pushes with TrapFocus, OverlayStack.Push sets KeyboardNavigation.TabNavigation/ControlTabNavigation to Cycle on the session’s root and focuses its first focusable descendant. |
Outside pointer-down closes via the same window-level (and per-input-root) PreviewMouseDown hook,
routed through OverlayDismissPolicy.FindOutsidePressTarget; not a keyboard interaction, but
shares the same dismiss-routing path.
UIA mechanism
Section titled “UIA mechanism”No automation peer of its own: NaviusOverlayLayer is a plain, non-focusable, hit-test-transparent-
until-active Grid, and OverlayStack/OverlaySession/OverlayOptions/OverlayDismissPolicy are
pure C# coordination types with no visual or automation surface. Each concrete family (Dialog,
AlertDialog, Drawer, Popover, PreviewCard, Tooltip, Toast) supplies its own AutomationPeer; see
their individual pages. The shared routing and focus-trap/restore logic is exercised directly by
OverlayStackTests.cs, and indirectly by every family’s own unit suite pushing through the same
Push/RequestClose path.
Web deltas
Section titled “Web deltas”There is no single webEquivalent family to diff against; the deltas below are against the shared
web base classes documented in the web contract’s own Overlays notes (OverlayPresence,
OverlayPopupBase, OverlayAnchoredPopupBase, OverlayPositionerBase).
- That inheritance chain (mount lifecycle, dismissable layer, anchored positioning, placement
flags all layered on one base) is replaced by two orthogonal WPF pieces instead of one base
class:
NaviusOverlayLayer(an in-windowGridportal target for layer-hosted surfaces) andOverlayStack/OverlaySession(window-level z-order plus Escape/outside-press routing, consumed directly byPopup-hosted controls viaRegisterInputRoot). There is no single shared base spanning both hosting styles. - The web’s six cancelable per-phase callbacks (
OnOpenAutoFocus,OnCloseAutoFocus,OnEscapeKeyDown,OnPointerDownOutside,OnFocusOutside,OnInteractOutside) collapse into one cancelableOverlaySession.Closingevent carrying anOverlayCloseReason. Known residual (M6 audit, first reported against AlertDialog but rooted here and applying to every family built on this substrate):OnOpenAutoFocushas no surviving equivalent at all, not folded intoClosing(which only covers the close side) or anywhere else inOverlaySession/OverlayOptions. Any “N hooks collapsed to one” framing on a consumer’s page therefore overstates what was ported by one hook; a fix would touch every consumer of this substrate, not one family, so it is reported here rather than fixed. - The web’s
data-starting-style/data-ending-styletwo-phase forced-reflow enter trick has no equivalent: WPF’sStoryboard.Begin/BeginAnimationdo not need it, so consumers drive enter/exit with plainDoubleAnimations keyed offIsOpeninstead. - Known residual (M6 audit, first reported against Dialog and Drawer but rooted here): a
non-modal surface does not restore focus to the pre-open element on close, because
OverlayStack.Pushonly capturesRestoreFocusTargetwhenTrapFocusis true, and every current consumer tiesTrapFocusto its own modality flag. The web contract restores focus in both the modal and non-modal paths.
Captures
Section titled “Captures”The Overlays Gallery page rendered at the pinned commit, in each theme.


