Skip to content

Confirm

Check a decision before an action that cannot be undone, and show progress while the confirmed action runs. Built on Modal.

  • ORD-1041

    Acme Corp

    €1 240.00
  • ORD-1042

    Birch & Co

    €380.00
  • ORD-1043

    Cedar Ltd

    €2 115.50
<script setup lang="ts">import { ref } from 'vue'import { Trash2 } from '@lucide/vue'import { UiStack, UiCard, UiList, UiListItem, UiIconButton, UiButton, UiSwitch, UiConfirm, UiToaster, useConfirm } from 'grani' const { confirm } = useConfirm() // A list of orders: deleting one asks first, waits for the server, then the row leaves.const ORDERS = [  { id: 'ORD-1041', client: 'Acme Corp', total: '€1 240.00' },  { id: 'ORD-1042', client: 'Birch & Co', total: '€380.00' },  { id: 'ORD-1043', client: 'Cedar Ltd', total: '€2 115.50' },] const orders = ref([...ORDERS]) const fail = ref(false) // The request behind the answer: a second the server takes, and its outcome.const deleteOrder = (id: string) =>  new Promise<void>((resolve, reject) =>    setTimeout(() => {      if (fail.value) return reject(new Error('offline'))      orders.value = orders.value.filter((o) => o.id !== id)      resolve()    }, 1000),  ) function remove(id: string) {  void confirm({    title: `Delete ${id}?`,    message: 'The order and its payments are removed. This cannot be undone.',    confirmLabel: 'Delete',    tone: 'danger',    action: () => deleteOrder(id),    success: `${id} deleted`,    error: `Could not delete ${id}`,  })} const restore = () => (orders.value = [...ORDERS])</script> <template>  <UiStack class="w-full max-w-md">    <UiCard :padded="false">      <UiList>        <UiListItem v-for="o in orders" :key="o.id" :title="o.id" :description="o.client">          <template #actions>            <span class="text-sm tabular-nums text-fg">{{ o.total }}</span>            <UiIconButton              :icon="Trash2"              variant="ghost"              tone="danger"              :ariaLabel="`Delete ${o.id}`"              @click="remove(o.id)"            />          </template>        </UiListItem>        <UiListItem v-if="!orders.length" title="No orders left">          <template #actions>            <UiButton variant="outline" size="xs" @click="restore">Bring them back</UiButton>          </template>        </UiListItem>      </UiList>    </UiCard>    <UiSwitch v-model="fail">The server fails</UiSwitch>  </UiStack>  <!-- Once, at the app's root: leave out what the app has there already. -->  <UiConfirm />  <UiToaster /></template>

Also called confirmation dialog, confirm dialog, alert dialog.

Usage

<UiConfirm />

With GraniResolver in the Vite config, a template needs no import: set it up once.

API

DecisionWhat it does
RootA question asked in an app without one is never shown and never answered, and dev says so once.
WindowIt asks in a UiModal with role="alertdialog". A press on the backdrop does not answer; Escape and the cross answer no. Asked from a window, it hides that window until it is answered; the window then comes back, with the focus on what asked.
ActionWith an action, the window sends the request, waits for it, and closes into a toast. If the request fails, the toast's Retry asks the question again. A new question answers the open one false; an action already on its way then reports nothing. Remove a row inside action, so the Retry after a failure removes it too; a UiTable then takes the focus from the window to the same button of the row now in its place. Every way out is refused while the request runs, so one that never settles holds the window open: give the action a timeout of its own, which fails it into the error toast.
LeavingA question is never left hanging. When the app has a router and the page changes, or when UiConfirm itself unmounts, the open question is answered no. A question whose action is already running is not cut off: the request's outcome answers it.

When to use another component

When you needTake
A short form or a detailModal
A question with a field in it (a reason to type)Modal role="alertdialog"
A recordCrud drawer
An outcome with nothing to answerA toast from useToast in Toaster