Dialog
StableModal overlay with focus trap, Escape-to-close, and automatic focus restoration to the trigger element. Dialog.Title is required for screen reader accessibility.
OverlayCompoundFocus trapVhyxSeal
Interactive example
Edit profile dialog
Import
tsx
import { Dialog } from '@vhyxui/react'
// Sub-components
// Dialog.Trigger — the element that opens the dialog
// Dialog.Portal — renders content outside DOM hierarchy
// Dialog.Overlay — the backdrop
// Dialog.Content — the dialog panel
// Dialog.Header — header slot
// Dialog.Footer — footer slot for actions
// Dialog.Title — required accessible title
// Dialog.Description — optional description
// Dialog.Close — button to closeVariants
Confirmation dialog (destructive)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state. |
defaultOpen | boolean | false | Default open state for uncontrolled usage. |
onOpenChange | (open: boolean) => void | — | Called when the open state changes. |
modal | boolean | true | When true, traps focus and renders the overlay. |
contract | Partial<ComponentContract> | — | VhyxSeal contract override. |
Sub-components: Dialog.Trigger accepts asChild. Dialog.Content accepts standard HTMLDivElement attributes.
Accessibility
-
role="dialog",aria-modal="true",aria-labelledbypointing to Dialog.Title. - Dialog.Title is required — a development warning is logged if absent.
- Focus is trapped inside the dialog while open. Tab cycles only within.
- On close, focus returns to the exact trigger element that opened the dialog.
- Escape always closes the dialog and restores focus.
- Overlay click closes the dialog by default.
Keyboard navigation
| Key | Action |
|---|---|
| Escape | Close the dialog and return focus to the trigger. |
| Tab | Cycle through focusable elements inside the dialog. |
| Shift + Tab | Reverse focus cycle within the dialog. |
Agent contract
Default VhyxSeal contract shipped with every Dialog.
Default contract
{
"type": "confirmation",
"intent": "open-dialog",
"description": "Opens a modal dialog overlay that interrupts the current workflow",
"requires": [],
"requiredPermissions": [],
"consequence": "Renders a blocking overlay — underlying page is inaccessible until dismissed",
"affects": [
"view"
],
"reversible": true,
"safetyLevel": "low",
"requiresConfirmation": false,
"destructive": false,
"contractVersion": "0.0.1",
"fingerprint": "vhyxs_4ae3824b"
}Theming
Override these CSS tokens to theme Dialog.
--vhyx-z-modalZ-index (400)
--vhyx-z-overlayOverlay z-index (300)
--vhyx-shadow-2xlContent shadow
--vhyx-radius-xlContent border radius
--vhyx-duration-slowEntry animation duration
--vhyx-easing-springContent slide-up easing
Examples
Controlled dialog
tsx
const [open, setOpen] = useState(false)
<Dialog open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<Button>Edit settings</Button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Settings</Dialog.Title>
{/* content */}
<Dialog.Footer>
<Button onClick={() => setOpen(false)}>Done</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Portal>
</Dialog>Open dialog programmatically
Open without a Trigger sub-component