AlertDialog

A confirmation dialog that needs an explicit answer.

<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>
vue

Usage

import { AlertDialog } from '@hina-ui/vue'
ts

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>
vue

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>
vue

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>
vue

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>
vue

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>
vue

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>
vue

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 error and keeps it open.

Accessibility

  • The panel is role="alertdialog", with the title and description linked through aria-labelledby and aria-describedby.
  • The panel carries aria-busy while 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