Dialog
Modal dialogs built on the native dialog element with a styled backdrop. Requires minimal JavaScript to open.
Usage
Use showModal() to open the dialog as a modal (with backdrop and focus trap). Use close() or a <form method="dialog"> to close it.
<button class="zui-button" onclick="document.getElementById('dialog-demo').showModal()">
Open dialog
</button>
<dialog id="dialog-demo" class="zui-dialog" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Dialog title</h2>
<p class="zui-dialog-description">A short description of the dialog content.</p>
</div>
<div class="zui-dialog-body">
<p>Dialog body content goes here.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog">
<button class="zui-button zui-button-variant-outline">Cancel</button>
</form>
<button class="zui-button" onclick="document.getElementById('dialog-demo').close()">Confirm</button>
</div>
</dialog> import { useState } from 'react'
import {
Dialog,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
Button,
} from '@mrmartineau/zui/react'
function MyComponent() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogHeader>
<DialogTitle>Dialog title</DialogTitle>
<DialogDescription>A short description of the dialog content.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog body content goes here.</p>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Confirm</Button>
</DialogFooter>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-demo').showModal()">Open dialog</Button>
<Dialog id="dialog-demo">
<DialogHeader>
<DialogTitle>Dialog title</DialogTitle>
<DialogDescription>A short description of the dialog content.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog body content goes here.</p>
</DialogBody>
<DialogFooter>
<form method="dialog">
<Button variant="outline">Cancel</Button>
</form>
<Button>Confirm</Button>
</DialogFooter>
</Dialog> import { createSignal } from 'solid-js'
import {
Dialog,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
Button,
} from '@mrmartineau/zui/solid'
function MyComponent() {
const [open, setOpen] = createSignal(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogHeader>
<DialogTitle>Dialog title</DialogTitle>
<DialogDescription>A short description of the dialog content.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog body content goes here.</p>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Confirm</Button>
</DialogFooter>
</Dialog>
</>
)
} <script lang="ts">
import {
Dialog,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
Button,
} from '@mrmartineau/zui/svelte'
let open = $state(false)
</script>
<Button onclick={() => (open = true)}>Open dialog</Button>
<Dialog bind:open>
<DialogHeader>
<DialogTitle>Dialog title</DialogTitle>
<DialogDescription>A short description of the dialog content.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog body content goes here.</p>
</DialogBody>
<DialogFooter>
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
<Button onclick={() => (open = false)}>Confirm</Button>
</DialogFooter>
</Dialog> <template>
<Button @click="open = true">Open dialog</Button>
<Dialog :open="open" @close="open = false">
<DialogHeader>
<DialogTitle>Dialog title</DialogTitle>
<DialogDescription>A short description of the dialog content.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog body content goes here.</p>
</DialogBody>
<DialogFooter>
<Button variant="outline" @click="open = false">Cancel</Button>
<Button @click="open = false">Confirm</Button>
</DialogFooter>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/vue'
const open = ref(false)
</script> Sizes
Control the dialog width with size variants.
<button class="zui-button" onclick="document.getElementById('dialog-sm').showModal()">Small</button>
<button class="zui-button" onclick="document.getElementById('dialog-md').showModal()">Medium (default)</button>
<button class="zui-button" onclick="document.getElementById('dialog-lg').showModal()">Large</button>
<button class="zui-button" onclick="document.getElementById('dialog-full').showModal()">Full</button>
<dialog id="dialog-sm" class="zui-dialog zui-dialog-size-sm">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Small dialog</h2>
</div>
<div class="zui-dialog-body"><p>Max width 24rem.</p></div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-md" class="zui-dialog">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Medium dialog</h2>
</div>
<div class="zui-dialog-body"><p>Max width 32rem (default).</p></div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-lg" class="zui-dialog zui-dialog-size-lg">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Large dialog</h2>
</div>
<div class="zui-dialog-body"><p>Max width 42rem.</p></div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-full" class="zui-dialog zui-dialog-size-full">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Full dialog</h2>
</div>
<div class="zui-dialog-body"><p>Full viewport size.</p></div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog> import { useState } from 'react'
import { Dialog, DialogHeader, DialogTitle, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/react'
function SizeDemo() {
const [size, setSize] = useState(null)
return (
<>
<Button onClick={() => setSize('sm')}>Small</Button>
<Button onClick={() => setSize('md')}>Medium (default)</Button>
<Button onClick={() => setSize('lg')}>Large</Button>
<Button onClick={() => setSize('full')}>Full</Button>
<Dialog open={!!size} size={size} onClose={() => setSize(null)}>
<DialogHeader>
<DialogTitle>{size} dialog</DialogTitle>
</DialogHeader>
<DialogBody><p>Dialog content.</p></DialogBody>
<DialogFooter>
<Button onClick={() => setSize(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-sm').showModal()">Small</Button>
<Button onclick="document.getElementById('dialog-md').showModal()">Medium (default)</Button>
<Button onclick="document.getElementById('dialog-lg').showModal()">Large</Button>
<Button onclick="document.getElementById('dialog-full').showModal()">Full</Button>
<Dialog id="dialog-sm" size="sm">
<DialogHeader><DialogTitle>Small dialog</DialogTitle></DialogHeader>
<DialogBody><p>Max width 24rem.</p></DialogBody>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-md">
<DialogHeader><DialogTitle>Medium dialog</DialogTitle></DialogHeader>
<DialogBody><p>Max width 32rem (default).</p></DialogBody>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-lg" size="lg">
<DialogHeader><DialogTitle>Large dialog</DialogTitle></DialogHeader>
<DialogBody><p>Max width 42rem.</p></DialogBody>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-full" size="full">
<DialogHeader><DialogTitle>Full dialog</DialogTitle></DialogHeader>
<DialogBody><p>Full viewport size.</p></DialogBody>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog> import { createSignal } from 'solid-js'
import { Dialog, DialogHeader, DialogTitle, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/solid'
function SizeDemo() {
const [size, setSize] = createSignal(null)
return (
<>
<Button onClick={() => setSize('sm')}>Small</Button>
<Button onClick={() => setSize('md')}>Medium (default)</Button>
<Button onClick={() => setSize('lg')}>Large</Button>
<Button onClick={() => setSize('full')}>Full</Button>
<Dialog open={!!size} size={size} onClose={() => setSize(null)}>
<DialogHeader>
<DialogTitle>{size} dialog</DialogTitle>
</DialogHeader>
<DialogBody><p>Dialog content.</p></DialogBody>
<DialogFooter>
<Button onClick={() => setSize(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} <script lang="ts">
import { Dialog, DialogHeader, DialogTitle, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/svelte'
let size = $state<'sm' | 'md' | 'lg' | 'full' | null>(null)
</script>
<Button onclick={() => (size = 'sm')}>Small</Button>
<Button onclick={() => (size = 'md')}>Medium (default)</Button>
<Button onclick={() => (size = 'lg')}>Large</Button>
<Button onclick={() => (size = 'full')}>Full</Button>
<Dialog open={!!size} {size} onclose={() => (size = null)}>
<DialogHeader>
<DialogTitle>{size} dialog</DialogTitle>
</DialogHeader>
<DialogBody><p>Dialog content.</p></DialogBody>
<DialogFooter>
<Button onclick={() => (size = null)}>Close</Button>
</DialogFooter>
</Dialog> <template>
<Button @click="size = 'sm'">Small</Button>
<Button @click="size = 'md'">Medium (default)</Button>
<Button @click="size = 'lg'">Large</Button>
<Button @click="size = 'full'">Full</Button>
<Dialog :open="!!size" :size="size" @close="size = null">
<DialogHeader>
<DialogTitle>{{ size }} dialog</DialogTitle>
</DialogHeader>
<DialogBody><p>Dialog content.</p></DialogBody>
<DialogFooter>
<Button @click="size = null">Close</Button>
</DialogFooter>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogBody, DialogFooter, Button } from '@mrmartineau/zui/vue'
const size = ref(null)
</script> Positions
Control where the dialog appears with the position variant. The default (center) uses margin: auto to center the dialog in the viewport. The other positions pin the dialog to a viewport edge and animate it into view from that side.
| Value | Description |
|---|---|
center | Centered in the viewport (default) |
central | Centered horizontally, anchored 60px from the top — useful for command palettes |
left | Full-height drawer sliding in from the left |
right | Full-height drawer sliding in from the right |
top | Full-width sheet sliding in from the top |
bottom | Full-width sheet sliding in from the bottom |
<button class="zui-button" onclick="document.getElementById('dialog-pos-central').showModal()">central</button>
<button class="zui-button" onclick="document.getElementById('dialog-pos-left').showModal()">left</button>
<button class="zui-button" onclick="document.getElementById('dialog-pos-right').showModal()">right</button>
<button class="zui-button" onclick="document.getElementById('dialog-pos-top').showModal()">top</button>
<button class="zui-button" onclick="document.getElementById('dialog-pos-bottom').showModal()">bottom</button>
<dialog id="dialog-pos-central" class="zui-dialog zui-dialog-position-central" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">central</h2>
<p class="zui-dialog-description">Anchored 60px from the top of the viewport.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-pos-left" class="zui-dialog zui-dialog-position-left" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">left</h2>
<p class="zui-dialog-description">Slides in from the left edge.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-pos-right" class="zui-dialog zui-dialog-position-right" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">right</h2>
<p class="zui-dialog-description">Slides in from the right edge.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-pos-top" class="zui-dialog zui-dialog-position-top" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">top</h2>
<p class="zui-dialog-description">Slides in from the top edge.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-pos-bottom" class="zui-dialog zui-dialog-position-bottom" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">bottom</h2>
<p class="zui-dialog-description">Slides in from the bottom edge.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog> import { useState } from 'react'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/react'
function PositionDemo() {
const [position, setPosition] = useState(null)
return (
<>
<Button onClick={() => setPosition('central')}>central</Button>
<Button onClick={() => setPosition('left')}>left</Button>
<Button onClick={() => setPosition('right')}>right</Button>
<Button onClick={() => setPosition('top')}>top</Button>
<Button onClick={() => setPosition('bottom')}>bottom</Button>
<Dialog open={!!position} position={position} onClose={() => setPosition(null)}>
<DialogHeader>
<DialogTitle>{position}</DialogTitle>
<DialogDescription>Slides in from the {position} edge.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setPosition(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-pos-central').showModal()">central</Button>
<Button onclick="document.getElementById('dialog-pos-left').showModal()">left</Button>
<Button onclick="document.getElementById('dialog-pos-right').showModal()">right</Button>
<Button onclick="document.getElementById('dialog-pos-top').showModal()">top</Button>
<Button onclick="document.getElementById('dialog-pos-bottom').showModal()">bottom</Button>
<Dialog id="dialog-pos-central" position="central">
<DialogHeader>
<DialogTitle>central</DialogTitle>
<DialogDescription>Anchored 60px from the top of the viewport.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-pos-left" position="left">
<DialogHeader>
<DialogTitle>left</DialogTitle>
<DialogDescription>Slides in from the left edge.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-pos-right" position="right">
<DialogHeader>
<DialogTitle>right</DialogTitle>
<DialogDescription>Slides in from the right edge.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-pos-top" position="top">
<DialogHeader>
<DialogTitle>top</DialogTitle>
<DialogDescription>Slides in from the top edge.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-pos-bottom" position="bottom">
<DialogHeader>
<DialogTitle>bottom</DialogTitle>
<DialogDescription>Slides in from the bottom edge.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog> import { createSignal } from 'solid-js'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/solid'
function PositionDemo() {
const [position, setPosition] = createSignal(null)
return (
<>
<Button onClick={() => setPosition('central')}>central</Button>
<Button onClick={() => setPosition('left')}>left</Button>
<Button onClick={() => setPosition('right')}>right</Button>
<Button onClick={() => setPosition('top')}>top</Button>
<Button onClick={() => setPosition('bottom')}>bottom</Button>
<Dialog open={!!position()} position={position()} onClose={() => setPosition(null)}>
<DialogHeader>
<DialogTitle>{position()}</DialogTitle>
<DialogDescription>Slides in from the {position()} edge.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setPosition(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} <script lang="ts">
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/svelte'
let position = $state<'central' | 'left' | 'right' | 'top' | 'bottom' | null>(null)
</script>
<Button onclick={() => (position = 'central')}>central</Button>
<Button onclick={() => (position = 'left')}>left</Button>
<Button onclick={() => (position = 'right')}>right</Button>
<Button onclick={() => (position = 'top')}>top</Button>
<Button onclick={() => (position = 'bottom')}>bottom</Button>
<Dialog open={!!position} {position} onclose={() => (position = null)}>
<DialogHeader>
<DialogTitle>{position}</DialogTitle>
<DialogDescription>Slides in from the {position} edge.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onclick={() => (position = null)}>Close</Button>
</DialogFooter>
</Dialog> <template>
<Button @click="position = 'central'">central</Button>
<Button @click="position = 'left'">left</Button>
<Button @click="position = 'right'">right</Button>
<Button @click="position = 'top'">top</Button>
<Button @click="position = 'bottom'">bottom</Button>
<Dialog :open="!!position" :position="position" @close="position = null">
<DialogHeader>
<DialogTitle>{{ position }}</DialogTitle>
<DialogDescription>Slides in from the {{ position }} edge.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button @click="position = null">Close</Button>
</DialogFooter>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/vue'
const position = ref(null)
</script> Close button
Add a close button inside the dialog using .zui-dialog-close.
<button class="zui-button" onclick="document.getElementById('dialog-close-btn').showModal()">
Open dialog
</button>
<dialog id="dialog-close-btn" class="zui-dialog">
<button
class="zui-button zui-button-variant-ghost zui-button-size-sm zui-button-icon zui-dialog-close"
onclick="document.getElementById('dialog-close-btn').close()"
aria-label="Close"
>
<i class="ph ph-x"></i>
</button>
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">With close button</h2>
<p class="zui-dialog-description">A close button is positioned in the top-right corner.</p>
</div>
<div class="zui-dialog-body">
<p>Content goes here.</p>
</div>
</dialog> import { useState } from 'react'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, Button } from '@mrmartineau/zui/react'
function MyComponent() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<Button
variant="ghost"
size="sm"
icon
className="zui-dialog-close"
aria-label="Close"
onClick={() => setOpen(false)}
>
<i className="ph ph-x" />
</Button>
<DialogHeader>
<DialogTitle>With close button</DialogTitle>
<DialogDescription>A close button is positioned in the top-right corner.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-close-btn').showModal()">Open dialog</Button>
<Dialog id="dialog-close-btn">
<Button
variant="ghost"
size="sm"
icon
class="zui-dialog-close"
aria-label="Close"
onclick="document.getElementById('dialog-close-btn').close()"
>
<i class="ph ph-x"></i>
</Button>
<DialogHeader>
<DialogTitle>With close button</DialogTitle>
<DialogDescription>A close button is positioned in the top-right corner.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
</Dialog> import { createSignal } from 'solid-js'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, Button } from '@mrmartineau/zui/solid'
function MyComponent() {
const [open, setOpen] = createSignal(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<Button
variant="ghost"
size="sm"
icon
class="zui-dialog-close"
aria-label="Close"
onClick={() => setOpen(false)}
>
<i class="ph ph-x" />
</Button>
<DialogHeader>
<DialogTitle>With close button</DialogTitle>
<DialogDescription>A close button is positioned in the top-right corner.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
</Dialog>
</>
)
} <script lang="ts">
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, Button } from '@mrmartineau/zui/svelte'
let open = $state(false)
</script>
<Button onclick={() => (open = true)}>Open dialog</Button>
<Dialog bind:open>
<Button
variant="ghost"
size="sm"
icon
class="zui-dialog-close"
aria-label="Close"
onclick={() => (open = false)}
>
<i class="ph ph-x"></i>
</Button>
<DialogHeader>
<DialogTitle>With close button</DialogTitle>
<DialogDescription>A close button is positioned in the top-right corner.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
</Dialog> <template>
<Button @click="setOpen(true)">Open dialog</Button>
<Dialog :open="open" @close="setOpen(false)">
<Button
variant="ghost"
size="sm"
icon
class="zui-dialog-close"
aria-label="Close"
@click="setOpen(false)"
>
<i class="ph ph-x" />
</Button>
<DialogHeader>
<DialogTitle>With close button</DialogTitle>
<DialogDescription>A close button is positioned in the top-right corner.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogBody, Button } from '@mrmartineau/zui/vue'
const open = ref(false)
</script> Closing behaviour
The closedby attribute controls how a dialog can be dismissed. ZUI defaults to closedby="any" on all components.
| Value | Behaviour |
|---|---|
any | Closed by backdrop click or Esc key (default) |
closerequest | Only closed by Esc key — backdrop click does nothing |
none | Cannot be dismissed by backdrop or Esc — requires a button or script |
<button class="zui-button" onclick="document.getElementById('dialog-closedby-any').showModal()">any (default)</button>
<button class="zui-button" onclick="document.getElementById('dialog-closedby-closerequest').showModal()">closerequest</button>
<button class="zui-button" onclick="document.getElementById('dialog-closedby-none').showModal()">none</button>
<dialog id="dialog-closedby-any" class="zui-dialog" closedby="any">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">closedby="any"</h2>
<p class="zui-dialog-description">Click the backdrop or press Esc to close.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-closedby-closerequest" class="zui-dialog" closedby="closerequest">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">closedby="closerequest"</h2>
<p class="zui-dialog-description">Only Esc closes this dialog — backdrop click does nothing.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog>
<dialog id="dialog-closedby-none" class="zui-dialog" closedby="none">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">closedby="none"</h2>
<p class="zui-dialog-description">Backdrop and Esc are both disabled — use the button below.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog"><button class="zui-button">Close</button></form>
</div>
</dialog> import { useState } from 'react'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/react'
function ClosedByDemo() {
const [closedby, setClosedby] = useState(null)
return (
<>
<Button onClick={() => setClosedby('any')}>any (default)</Button>
<Button onClick={() => setClosedby('closerequest')}>closerequest</Button>
<Button onClick={() => setClosedby('none')}>none</Button>
<Dialog open={!!closedby} closedby={closedby} onClose={() => setClosedby(null)}>
<DialogHeader>
<DialogTitle>closedby="{closedby}"</DialogTitle>
<DialogDescription>
{closedby === 'any' && 'Click the backdrop or press Esc to close.'}
{closedby === 'closerequest' && 'Only Esc closes this dialog — backdrop click does nothing.'}
{closedby === 'none' && 'Backdrop and Esc are both disabled — use the button below.'}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setClosedby(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-closedby-any').showModal()">any (default)</Button>
<Button onclick="document.getElementById('dialog-closedby-cr').showModal()">closerequest</Button>
<Button onclick="document.getElementById('dialog-closedby-none').showModal()">none</Button>
<Dialog id="dialog-closedby-any" closedby="any">
<DialogHeader>
<DialogTitle>closedby="any"</DialogTitle>
<DialogDescription>Click the backdrop or press Esc to close.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-closedby-cr" closedby="closerequest">
<DialogHeader>
<DialogTitle>closedby="closerequest"</DialogTitle>
<DialogDescription>Only Esc closes this dialog — backdrop click does nothing.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog>
<Dialog id="dialog-closedby-none" closedby="none">
<DialogHeader>
<DialogTitle>closedby="none"</DialogTitle>
<DialogDescription>Backdrop and Esc are both disabled — use the button below.</DialogDescription>
</DialogHeader>
<DialogFooter><form method="dialog"><Button>Close</Button></form></DialogFooter>
</Dialog> import { createSignal } from 'solid-js'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/solid'
function ClosedByDemo() {
const [closedby, setClosedby] = createSignal(null)
return (
<>
<Button onClick={() => setClosedby('any')}>any (default)</Button>
<Button onClick={() => setClosedby('closerequest')}>closerequest</Button>
<Button onClick={() => setClosedby('none')}>none</Button>
<Dialog open={!!closedby} closedby={closedby} onClose={() => setClosedby(null)}>
<DialogHeader>
<DialogTitle>closedby="{closedby}"</DialogTitle>
<DialogDescription>
{closedby === 'any' && 'Click the backdrop or press Esc to close.'}
{closedby === 'closerequest' && 'Only Esc closes this dialog — backdrop click does nothing.'}
{closedby === 'none' && 'Backdrop and Esc are both disabled — use the button below.'}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setClosedby(null)}>Close</Button>
</DialogFooter>
</Dialog>
</>
)
} <script lang="ts">
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/svelte'
let closedby = $state<'any' | 'closerequest' | 'none' | null>(null)
</script>
<Button onclick={() => (closedby = 'any')}>any (default)</Button>
<Button onclick={() => (closedby = 'closerequest')}>closerequest</Button>
<Button onclick={() => (closedby = 'none')}>none</Button>
<Dialog open={!!closedby} {closedby} onclose={() => (closedby = null)}>
<DialogHeader>
<DialogTitle>closedby="{closedby}"</DialogTitle>
<DialogDescription>
{#if closedby === 'any'}Click the backdrop or press Esc to close.{/if}
{#if closedby === 'closerequest'}Only Esc closes this dialog — backdrop click does nothing.{/if}
{#if closedby === 'none'}Backdrop and Esc are both disabled — use the button below.{/if}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onclick={() => (closedby = null)}>Close</Button>
</DialogFooter>
</Dialog> <template>
<Button @click="closedby = 'any'">any (default)</Button>
<Button @click="closedby = 'closerequest'">closerequest</Button>
<Button @click="closedby = 'none'">none</Button>
<Dialog :open="!!closedby" :closedby="closedby" @close="closedby = null">
<DialogHeader>
<DialogTitle>closedby="{{ closedby }}"</DialogTitle>
<DialogDescription>
<template v-if="closedby === 'any'">Click the backdrop or press Esc to close.</template>
<template v-else-if="closedby === 'closerequest'">Only Esc closes this dialog — backdrop click does nothing.</template>
<template v-else-if="closedby === 'none'">Backdrop and Esc are both disabled — use the button below.</template>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button @click="closedby = null">Close</Button>
</DialogFooter>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/vue'
const closedby = ref(null)
</script> With form
Use <form method="dialog"> for native form submission that closes the dialog and sets returnValue.
<button class="zui-button" onclick="document.getElementById('dialog-form').showModal()">
Delete item
</button>
<dialog id="dialog-form" class="zui-dialog">
<div class="zui-dialog-header">
<h2 class="zui-dialog-title">Are you sure?</h2>
<p class="zui-dialog-description">This action cannot be undone.</p>
</div>
<div class="zui-dialog-footer">
<form method="dialog">
<button class="zui-button zui-button-variant-outline" value="cancel">Cancel</button>
</form>
<form method="dialog">
<button class="zui-button zui-button-color-destructive" value="confirm">Delete</button>
</form>
</div>
</dialog> import { useState } from 'react'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/react'
function MyComponent() {
const [open, setOpen] = useState(false)
return (
<>
<Button onClick={() => setOpen(true)}>Delete item</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="destructive" onClick={() => setOpen(false)}>Delete</Button>
</DialogFooter>
</Dialog>
</>
)
} ---
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/astro'
---
<Button onclick="document.getElementById('dialog-form').showModal()">Delete item</Button>
<Dialog id="dialog-form">
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<form method="dialog">
<Button variant="outline" value="cancel">Cancel</Button>
</form>
<form method="dialog">
<Button color="destructive" value="confirm">Delete</Button>
</form>
</DialogFooter>
</Dialog> import { createSignal } from 'solid-js'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/solid'
function MyComponent() {
const [open, setOpen] = createSignal(false)
return (
<>
<Button onClick={() => setOpen(true)}>Delete item</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button color="destructive" onClick={() => setOpen(false)}>Delete</Button>
</DialogFooter>
</Dialog>
</>
)
} <script lang="ts">
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/svelte'
let open = $state(false)
</script>
<Button onclick={() => (open = true)}>Delete item</Button>
<Dialog bind:open>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
<Button color="destructive" onclick={() => (open = false)}>Delete</Button>
</DialogFooter>
</Dialog> <template>
<Button @click="setOpen(true)">Delete item</Button>
<Dialog :open="open" @close="setOpen(false)">
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" @click="setOpen(false)">Cancel</Button>
<Button color="destructive" @click="setOpen(false)">Delete</Button>
</DialogFooter>
</Dialog>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button } from '@mrmartineau/zui/vue'
const open = ref(false)
</script> CSS custom properties
| Property | Default | Description |
|---|---|---|
--zui-dialog-bg | var(--color-surface) | Background colour |
--zui-dialog-border | var(--color-border) | Border colour |
--zui-dialog-radius | var(--radius-xl) | Border radius |
--zui-dialog-shadow | var(--shadow-2xl) | Box shadow |
--zui-dialog-padding | var(--space-md) | Padding inside the dialog |
--zui-dialog-max-width | 32rem | Maximum width |