Dialog
Modal component that uses native HTML5 dialog functionality.
Dialogs are fully controlled by the web platform — opening and closing is handled with native Invoker Commands (command / commandfor) and the closedby attribute, so no JavaScript API is needed.
Installation
Follow instructions for individual framework usage below
Browser support
The dialog element is Baseline. For older browsers cover Invoker Commands with invokers-polyfill — the closedby attribute is covered by the side-effect script below. See Polyfills.
Styles
Variants
Props
Examples
Basic
<button class="x-button" command="show-modal" commandfor="dialogBasic">Show dialog</button>
<dialog class="x-dialog" id="dialogBasic" closedby="any">
<form class="x-dialog-content" method="dialog">
<div class="x-heading">Example dialog</div>
<br>
<div class="x-text">
<p>You can close this dialog with Esc, clicking outside, or by form submit</p>
</div>
<br>
<button class="x-button">Close dialog</button>
</form>
</dialog><button class="x-button" data-controller="x-button" command="show-modal" commandfor="dialogBasic">Show dialog</button>
<dialog class="x-dialog" id="dialogBasic" closedby="any">
<form class="x-dialog-content" method="dialog">
<div class="x-heading">Example dialog</div>
<br>
<div class="x-text">
<p>You can close this dialog with Esc, clicking outside, or by form submit</p>
</div>
<br>
<button class="x-button" data-controller="x-button">Close dialog</button>
</form>
</dialog><script setup lang="ts">
import { useId } from 'vue'
import { Button } from '@/components/button'
import { Dialog, DialogContent } from '@/components/dialog'
import { Heading } from '@/components/heading'
const dialogBasic = useId()
</script>
<template>
<Button command="show-modal" :commandfor="dialogBasic">
Open Dialog
</Button>
<Dialog :id="dialogBasic" closedby="any">
<DialogContent>
<Heading>Hello there!</Heading>
<Button class="muted accent-main" command="close" :commandfor="dialogBasic">
Close me!
</Button>
</DialogContent>
</Dialog>
</template>import { useId } from 'react'
import { Button } from '@/components/button'
import { Dialog, DialogContent } from '@/components/dialog'
import { Heading } from '@/components/heading'
export function Example() {
const dialogBasic = useId()
return (
<>
<Button command="show-modal" commandfor={dialogBasic}>
Open Dialog
</Button>
<Dialog id={dialogBasic}>
<DialogContent>
<Heading>Hello there!</Heading>
<Button className="muted accent-main" command="close" commandfor={dialogBasic}>
Close me!
</Button>
</DialogContent>
</Dialog>
</>
)
}Accessibility
The native dialog element provides accessibility out of the box — showModal() moves focus into the dialog, makes the rest of the page inert, closes on Esc and returns focus to the trigger on close. Light-dismiss behavior is controlled declaratively with the closedby attribute.
JavaScript
Always include the dialog side-effect script in every installation variant:
import '/src/components/dialog/index.js'There is no JavaScript API — dialogs use the native HTMLDialogElement interface. If you need to control a dialog programmatically, use the native methods directly
document.querySelector('#dialogExample').showModal()
document.querySelector('#dialogExample').close()The winduum/src/components/dialog side-effect script additionally
- closes an open dialog when clicking its backdrop, based on the
closedbyattribute (fallback for browsers without nativeclosedbysupport) - keeps
--default-scrollbar-widthon<html>updated via aResizeObserver, which the dialog CSS uses to compensate for the hidden page scrollbar