Dialog
Dialogs shown on top of the page: a general modal, a confirmation dialog, and a drawer that opens from the edge of the screen.
UiModal
A modal dialog shown on top of the page. While it is open, the rest of the page cannot be used, keeping attention on the dialog.
Code
<script setup lang="ts">
import { shallowRef } from 'vue'
import { UiButton, UiModal } from '@wing-land/ui'
const open = shallowRef(false)
const draft = shallowRef('')
const legacy = shallowRef<{ open: () => void; close: () => void } | null>(null)
function confirmDiscard() {
return draft.value === '' || window.confirm('Discard your changes?')
}
</script>
<template>
<div class="demo">
<UiButton @click="open = true">Open with v-model</UiButton>
<UiButton @click="legacy?.open()">Open with ref</UiButton>
<UiModal
v-model:open="open"
title="Rename item"
description="Closing with unsaved input asks for confirmation."
:before-close="confirmDiscard"
>
<input v-model="draft" aria-label="New name" />
<template #footer>
<UiButton variant="ghost" @click="open = false">Cancel</UiButton>
<UiButton variant="solid" @click="((draft = ''), (open = false))">Save</UiButton>
</template>
</UiModal>
<UiModal ref="legacy" label="Example">
<p>Close it with Escape or the close button.</p>
<UiButton @click="legacy?.close()">Close</UiButton>
</UiModal>
</div>
</template>
<style scoped>
.demo {
display: flex;
gap: 8px;
padding: 1rem 0;
}
</style>Usage
Open and close it with v-model:open or the exposed open() and close() methods. To ask whether unsaved input can be discarded, return false from beforeClose to keep it open. When dismissible is false, neither Escape, the backdrop, nor the close button closes it, and the close button is hidden.
Accessibility
Renders a native <dialog> element opened with showModal(), so focus and screen readers cannot reach anything outside it. While open, it stops the page behind it from scrolling, and when closed, it returns focus to the element that was focused before. title or label becomes its accessible name. With nested modals, scrolling resumes when the last one closes.
API
Props
| Prop | Default | Type |
|---|---|---|
labelAccessible name of the dialog when there is no | — | string |
titleHeading. Becomes the accessible name of the dialog. | — | string |
descriptionDescription below the heading. Linked with | — | string |
sizeWidth. | 'default' | 'default' | 'small' | 'large' |
placementPosition. | 'center' | 'end' | 'start' | 'center' |
roleUse | 'dialog' | 'dialog' | 'alertdialog' |
closeLabelAccessible name of the close button. Defaults to the locale text. | — | string |
dismissibleWhen false, user actions do not close the dialog. | true | boolean |
beforeCloseCalled when the user tries to close the dialog. Return false to keep it open. | — | (reason: UiModalCloseReason) => boolean | Promise<boolean> |
openv-model:openWhether the dialog is open. | false | boolean |
Events
| Event | Payload |
|---|---|
closeEmitted when the dialog closes. | — |
openEmitted when the dialog opens. | — |
update:openEmitted when the | (value: boolean) |
Slots
| Slot | Slot props |
|---|---|
headerContent placed where the heading goes. Use it instead of | — |
defaultBody. | — |
footerActions placed at the bottom right. | — |
Exposed
| Name | Type |
|---|---|
closeCloses the dialog without calling | () => void |
openOpens the dialog. | () => void |
UiConfirmDialog
A small dialog that asks for confirmation before an action that cannot be undone.
Code
<script setup lang="ts">
import { shallowRef } from 'vue'
import { UiButton, UiConfirmDialog, useConfirm } from '@wing-land/ui'
const { dialog, confirm } = useConfirm()
const result = shallowRef('')
async function remove() {
const ok = await confirm({
title: 'Delete this item?',
description: 'Deleted items cannot be restored.',
confirmLabel: 'Delete',
tone: 'danger',
})
result.value = ok ? 'Deleted' : 'Canceled'
}
</script>
<template>
<div class="demo">
<UiButton tone="danger" @click="remove">Delete item</UiButton>
<p role="status">{{ result }}</p>
<UiConfirmDialog v-bind="dialog" />
</div>
</template>
<style scoped>
.demo {
padding: 1rem 0;
}
</style>Usage
The confirm button only emits confirm; closing the dialog is up to your app. While an async action runs, set loading to true to wait for it without the dialog closing.
useConfirm
useConfirm() lets you use one dialog placed in the same component as a Promise.
const { dialog, confirm } = useConfirm()
// Template: <UiConfirmDialog v-bind="dialog" />
if (await confirm({ title: 'Delete this item?', confirmLabel: 'Delete', tone: 'danger' })) remove()Closing with the cancel button, Escape, or the close button resolves to false.
Accessibility
Renders a UiModal with role="alertdialog" and title as its accessible name. To prevent accidental confirmation, initial focus goes to the cancel button.
API
Props
| Prop | Default | Type |
|---|---|---|
titleRequiredThe question. Becomes the accessible name of the dialog. | — | string |
descriptionExtra explanation. | — | string |
confirmLabelText of the confirm button. Defaults to the locale text. | — | string |
cancelLabelText of the cancel button. Defaults to the locale text. | — | string |
toneWith | 'neutral' | 'danger' | 'neutral' |
loadingPuts the confirm button in a busy state and prevents closing. | false | boolean |
openv-model:openWhether the dialog is open. | false | boolean |
Events
| Event | Payload |
|---|---|
update:openEmitted when the | (value: boolean) |
confirmEmitted when the confirm button is pressed. | — |
cancelEmitted when the dialog closes any other way (cancel button, Escape, close button, or backdrop). | — |
Slots
| Slot | Slot props |
|---|---|
defaultContent added below the description. | — |
UiDrawer
A panel that opens from the left or right edge of the screen. Use it for navigation on narrow screens or to show details.
Code
<script setup lang="ts">
import { shallowRef } from 'vue'
import { UiButton, UiDrawer } from '@wing-land/ui'
const navigation = shallowRef(false)
const backlinks = shallowRef(false)
</script>
<template>
<div class="demo">
<UiButton icon="menu" @click="navigation = true">Navigation</UiButton>
<UiButton @click="backlinks = true">Backlinks</UiButton>
<UiDrawer v-model:open="navigation" title="Workspace">
<p>Inbox, projects and archived notes.</p>
</UiDrawer>
<UiDrawer v-model:open="backlinks" side="end" title="Backlinks" size="large">
<p>3 notes link to this note.</p>
</UiDrawer>
</div>
</template>
<style scoped>
.demo {
display: flex;
gap: 8px;
padding: 1rem 0;
}
</style>Usage
Choose the side with side and the width with size. Opening and closing, beforeClose, and dismissible work the same as UiModal.
Accessibility
Uses the same native <dialog> element as UiModal, and handles focus, background scrolling, and returning focus after closing the same way. title or label becomes its accessible name.
API
Props
| Prop | Default | Type |
|---|---|---|
sideSide to open from. | 'start' | 'end' | 'start' |
labelAccessible name when there is no | — | string |
titleHeading. | — | string |
descriptionDescription below the heading. | — | string |
sizeWidth. | 'default' | 'default' | 'small' | 'large' |
closeLabelAccessible name of the close button. Defaults to the locale text. | — | string |
dismissibleWhen false, user actions do not close the drawer. | true | boolean |
beforeCloseCalled when the user tries to close the drawer. Return false to keep it open. | — | (reason: UiModalCloseReason) => boolean | Promise<boolean> |
openv-model:openWhether the drawer is open. | false | boolean |
Events
| Event | Payload |
|---|---|
closeEmitted when the drawer closes. | — |
openEmitted when the drawer opens. | — |
update:openEmitted when the | (value: boolean) |
Slots
| Slot | Slot props |
|---|---|
defaultBody. | any |
headerContent placed where the heading goes. | any |
footerActions placed at the bottom. | any |
Exposed
| Name | Type |
|---|---|
closeCloses the drawer without calling | () => void | undefined |
openOpens the drawer. | () => void |