<script setup lang="ts">
import { ref } from 'vue'
import { AlertDialog, Button, Stack, Text } from '@hina-ui/vue'
const removed = ref(false)
async function remove() {
await new Promise(resolve => setTimeout(resolve, 800))
removed.value = true
}
</script>
<template>
<Stack gap="sm" align="start">
<AlertDialog
title="Delete this article?"
description="This cannot be undone, and the comments under it are deleted as well."
tone="danger"
confirm-text="Delete"
@confirm="remove"
>
<Button variant="outline" tone="danger">Delete article</Button>
</AlertDialog>
<Text v-if="removed" tone="muted" size="sm">The article was deleted.</Text>
</Stack>
</template>
Usage
import { AlertDialog } from '@hina-ui/vue'
The alert dialog asks one question: title is the question, description spells out the consequence, and a fixed pair of buttons sits at the bottom, with cancelText and confirmText to change their text. The default slot is the trigger. Clicking the scrim does not close it; Esc or Cancel does, and there is no close button in the corner because answering is how it closes.
<script setup lang="ts">
import { ref } from 'vue'
import { AlertDialog, Button, Stack, Text } from '@hina-ui/vue'
const published = ref(false)
</script>
<template>
<Stack gap="sm" align="start">
<AlertDialog
title="Publish this article?"
description="Once published, everyone can see it."
confirm-text="Publish"
cancel-text="Not yet"
@confirm="published = true"
>
<Button variant="outline" tone="neutral">Publish</Button>
</AlertDialog>
<Text v-if="published" tone="muted" size="sm">The article was published.</Text>
</Stack>
</template>
Examples
Destructive actions
tone="danger" gives the confirm button the danger color for irreversible actions such as deleting.
<script setup lang="ts">
import { AlertDialog, Button } from '@hina-ui/vue'
</script>
<template>
<AlertDialog
title="Delete your account?"
description="The account and all of its data are deleted permanently after 30 days; signing in before then cancels it."
tone="danger"
confirm-text="Delete account"
>
<Button variant="outline" tone="danger">Delete account</Button>
</AlertDialog>
</template>
Confirmation countdown
confirmDelay sets the wait before confirmation in seconds and defaults to 0. Each opening starts a new countdown. The confirm button is disabled and shows the remaining seconds, then returns to its original label and enabled state. Cancel and Esc remain available while waiting.
Changing confirmDelay while open restarts the countdown with the new value; setting it to 0 removes the wait immediately. Positive fractions round up. Non-positive or non-finite values are treated as 0. A failed confirmation can be retried immediately.
<script setup lang="ts">
import { AlertDialog, Button } from '@hina-ui/vue'
</script>
<template>
<AlertDialog
title="Confirm action?"
description="The confirm button becomes available when the countdown ends."
tone="danger"
confirm-text="Confirm"
:confirm-delay="3"
>
<Button variant="outline" tone="neutral">Confirmation countdown</Button>
</AlertDialog>
</template>
Asynchronous confirm
Pass the confirmation handler through onConfirm or @confirm. When it returns a Promise or PromiseLike, the dialog waits for completion before closing. The confirm button shows a loading indicator, and Cancel and Esc are unavailable while pending.
The component catches synchronous throws and asynchronous rejections and emits the original reason through error. The dialog stays open and the buttons become available for retry. Use @error to update an error message.
<script setup lang="ts">
import { ref } from 'vue'
import { AlertDialog, Button, Stack, Switch, Text } from '@hina-ui/vue'
const fail = ref(false)
const status = ref('')
async function archive() {
status.value = ''
await new Promise(resolve => setTimeout(resolve, 1000))
if (fail.value) {
throw new Error('Archiving failed, please try again later.')
}
status.value = 'Archived.'
}
function handleError(error: unknown) {
status.value = error instanceof Error ? error.message : String(error)
}
</script>
<template>
<Stack gap="md" align="start">
<Switch v-model="fail">Simulate a failure</Switch>
<AlertDialog
title="Archive this project?"
description="An archived project becomes read-only and can be restored at any time."
confirm-text="Archive"
@confirm="archive"
@error="handleError"
>
<Button variant="outline" tone="neutral">Archive project</Button>
<template #content>
<Text v-if="status" tone="muted" size="sm">{{ status }}</Text>
</template>
</AlertDialog>
</Stack>
</template>
Controlled
open supports two-way binding. Without a default slot no trigger is rendered and the dialog can only be opened from outside.
<script setup lang="ts">
import { ref } from 'vue'
import { AlertDialog, Button, Inline } from '@hina-ui/vue'
const open = ref(false)
</script>
<template>
<Inline gap="sm">
<Button variant="outline" tone="neutral" @click="open = true">Open from outside</Button>
<AlertDialog
v-model:open="open"
title="Leave this page?"
description="Unsaved changes will be lost."
/>
</Inline>
</template>
Size and placement
size defaults to sm, 384 pixels wide, and md is 448; placement works as in Dialog, centered on wide screens and docked to the bottom on narrow ones when unset.
<script setup lang="ts">
import { AlertDialog, Button, Inline } from '@hina-ui/vue'
</script>
<template>
<Inline gap="sm">
<AlertDialog
title="Empty the trash?"
description="The 12 files in the trash are deleted permanently."
size="md"
>
<Button variant="outline" tone="neutral">md size</Button>
</AlertDialog>
<AlertDialog
title="Empty the trash?"
description="The 12 files in the trash are deleted permanently."
placement="center"
>
<Button variant="outline" tone="neutral">Always centered</Button>
</AlertDialog>
<AlertDialog
title="Empty the trash?"
description="The 12 files in the trash are deleted permanently."
placement="bottom"
>
<Button variant="outline" tone="neutral">Always at the bottom</Button>
</AlertDialog>
</Inline>
</template>
Behavior
- Focus lands on the Cancel button when the dialog opens, so an accidental Enter does nothing; focus returns to the trigger on close.
- Clicking the scrim does not close it; Esc closes it and counts as cancel.
- The page stops scrolling and focus is trapped inside the panel while it is open.
- When the confirm handler returns a Promise the dialog stays busy until it settles. Success closes it; failure emits
errorand keeps it open.
Accessibility
- The panel is
role="alertdialog", with the title and description linked througharia-labelledbyandaria-describedby. - The panel carries
aria-busywhile busy. - Button text comes from the current locale and can be replaced.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Required. The question |
description | string | — | Supporting text |
confirmText | string | locale | Text of the confirm button |
confirmDelay | number | 0 | Seconds to wait before confirmation on each opening |
cancelText | string | locale | Text of the cancel button |
tone | 'accent' | 'danger' | 'accent' | Tone of the confirm button |
size | 'sm' | 'md' | 'sm' | Maximum width of the panel |
placement | 'center' | 'bottom' | — | Follows the screen width when unset |
onConfirm | () => unknown | — | Confirmation handler; may return a Promise or PromiseLike |
open | boolean | — | Whether it is open, supports two-way binding |
class | string | — | Classes appended to the panel |
Slots
| Slot | Description |
|---|---|
| default | The trigger |
content | Content between the description and buttons |
Events
| Event | Payload | Description |
|---|---|---|
confirm | — | Confirm was clicked; a Promise from the handler is awaited |
cancel | — | Cancel was clicked |
error | unknown | Original confirmation failure; keeps the dialog open |