---
area: "Learn"
parent: "Forms chapter"
url: "https://unpoly.com/learn/confirmation-dialogs"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Forms chapter](https://unpoly.com/learn/forms.md)</nav>

# Confirmation dialogs

Before a link or form does something that is hard to undo, like deleting a record, Unpoly can ask the user to confirm. When the user cancels, nothing is rendered and the page stays as it was.

<nav aria-label="Contents">

#### Contents

- [Confirming a link](#links)
- [Confirming a form submission](#forms)
- [Confirming from JavaScript](#script)
- [Confirming before closing an overlay](#overlays)
- [Rails helpers and `[data-confirm]`](#data-confirm)
- [Styled confirmation dialogs](#styled)

</nav>

## Confirming a link {#links}

To ask before a link is followed, set an [`[up-confirm]`](https://unpoly.com/api/up-follow.md#up-confirm) attribute with the question:

```html
<a href="/projects/5" up-method="delete" up-follow up-confirm="Really delete this project?">Delete project</a>
```

When the user clicks the link, Unpoly shows the question in the browser's [native confirmation dialog](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm). Only when the user confirms does Unpoly send the request and update the page.

The native dialog blocks the page until the user answers. Its look and button labels are up to the browser. To show your own dialog instead, see [styled confirmation dialogs](#styled).

## Confirming a form submission {#forms}

On a form that is [submitted through Unpoly](https://unpoly.com/learn/submitting-forms.md), set `[up-confirm]` on the `<form>` element:

```html
<form method="post" action="/projects/5/archive" up-submit up-confirm="Archive this project?">
  ...
</form>
```

To ask only for one action, set the attribute on a submit button instead. The user is only asked when they submit the form with that button:

```html
<form method="post" action="/proposal" up-submit>
  <button type="submit" name="decision" value="accept">Accept</button>
  <button type="submit" name="decision" value="reject" up-confirm="Really reject?">Reject</button>
</form>
```

[Validating](https://unpoly.com/learn/validation.md) a field with [`[up-validate]`](https://unpoly.com/api/up-validate.md) never asks.

## Confirming from JavaScript {#script}

Functions that render, like [`up.render()`](https://unpoly.com/api/up.render.md), [`up.follow()`](https://unpoly.com/api/up.follow.md), [`up.submit()`](https://unpoly.com/api/up.submit.md) or [`up.navigate()`](https://unpoly.com/api/up.navigate.md), accept a `{ confirm }` option:

```js
up.navigate({ url: '/projects/5', method: 'delete', confirm: 'Really delete this project?' })
```

When the user cancels, the returned promise rejects with an `up.Aborted` error.

## Confirming before closing an overlay {#overlays}

An [`[up-dismiss]`](https://unpoly.com/api/up-dismiss.md) or [`[up-accept]`](https://unpoly.com/api/up-accept.md) button can ask before it [closes its overlay](https://unpoly.com/learn/closing-overlays.md):

```html
<button up-dismiss up-confirm="Discard your changes?">Cancel</button>
```

From JavaScript, pass a `{ confirm }` option to [`up.layer.dismiss()`](https://unpoly.com/api/up.layer.dismiss.md) or [`up.layer.accept()`](https://unpoly.com/api/up.layer.accept.md). When the user cancels, the overlay stays open and the function throws an `up.Aborted` error.

To ask when the user closes the overlay with the `Escape` key, a click on the backdrop or the `×` button, see [keeping an overlay open](https://unpoly.com/learn/closing-overlays.md#keeping-open). To ask only when a form has unsaved changes, see [Preventing loss of unsaved changes](https://unpoly.com/learn/unsaved-changes.md).

## Rails helpers and `[data-confirm]` {#data-confirm}

Unpoly also accepts the question in a `[data-confirm]` attribute, which Rails helpers like `link_to` and `button_to` render:

```html
<a href="/projects/5" data-method="delete" data-confirm="Really delete this project?" up-follow>Delete project</a>
```

On links and forms that Unpoly handles, `[data-confirm]` works like `[up-confirm]`. When your app also loads Rails UJS, Unpoly takes over the attribute so the user isn't asked twice.

## Styled confirmation dialogs {#styled}

The browser's dialog cannot be styled. To ask in an [overlay](https://unpoly.com/learn/overlays.md) instead, give your links and forms a `[custom-confirm]` attribute:

```html
<a href="/projects/5" up-method="delete" up-follow custom-confirm="Really delete this project?">Delete project</a>

<form method="post" action="/projects/5/archive" up-submit custom-confirm="Archive this project?">
  ...
</form>
```

The attribute is not part of Unpoly. It is a hook for this listener:

```js
up.on('up:link:follow up:form:submit', '[custom-confirm]', (event, element) => {
  const message = element.getAttribute('custom-confirm')
  const content = `
    <p>${up.util.escapeHTML(message)}</p>
    <button up-accept>OK</button>
    <button up-dismiss>Cancel</button>
  `
  event.preventDefault()                    // don't follow the link or submit the form yet
  up.layer.ask({ content }).then(
    () => up.render(event.renderOptions),   // on accept: render with the same options
    () => {}                                // on dismiss: do nothing
  )
})
```

Before Unpoly follows a link, it emits [`up:link:follow`](https://unpoly.com/api/up:link:follow.md). Before it submits a form, it emits [`up:form:submit`](https://unpoly.com/api/up:form:submit.md). The listener prevents the event, so nothing is rendered yet. Instead it opens an overlay with [`up.layer.ask()`](https://unpoly.com/api/up.layer.ask.md), which returns a promise for the user's answer. The overlay's buttons [accept or dismiss it](https://unpoly.com/learn/closing-overlays.md). When the user accepts, the listener renders with the original options from `event.renderOptions`, which follows the link or submits the form.

For a form, `event.renderOptions` also remembers which button was pressed, so the form is submitted with its name and value.

The message is passed through [`up.util.escapeHTML()`](https://unpoly.com/api/up.util.escapeHTML.md), so a message with characters like `<` cannot inject HTML. To keep the dialog's HTML out of your JavaScript, you can also pass a [template](https://unpoly.com/learn/templates.md) selector as `{ content }`.

The recipe uses its own attribute instead of `[up-confirm]`. With `[up-confirm]`, `event.renderOptions` would contain a `{ confirm }` option, and the browser's dialog would appear after your own.

The browser's dialog blocks the page until the user answers. A styled dialog doesn't, so the page can change while it is open, e.g. when a [polling](https://unpoly.com/learn/polling.md) response arrives. The link is then followed on the changed page. If the link itself was replaced in the meantime, nothing is rendered and [`up.render()`](https://unpoly.com/api/up.render.md) rejects with an `up.CannotMatch` error.

<nav aria-label="Reading order">

- [Previous: Disabling forms while working](https://unpoly.com/learn/disabling-forms.md)
- [Next: Preventing loss of unsaved changes](https://unpoly.com/learn/unsaved-changes.md)

</nav>

