Alert Dialog

Renders an alert dialog that requires the immediate attention and response of the user.

Read more Read less

This component is meant for situations where critical information must be conveyed, and an explicit response is required from the user. It is typically used for confirmation dialogs, warning messages, error notifications, and other scenarios where an immediate decision is necessary.

For non-critical dialogs, such as those containing forms or additional information, use Doggo.Components.build_modal/1 instead.

Maturity: Developing

Usage

<.alert_dialog id="end-session-modal">
  <:title>End Training Session Early?</:title>
  <p>
    Are you sure you want to end the current training session with Bella?
    She's making great progress today!
  </p>
  <:footer>
    <.button phx-click="end-session">
      Yes, end session
    </.button>
    <.button phx-click={JS.exec("data-cancel", to: "#end-session-modal")}>
      No, continue training
    </.button>
  </:footer>
</.alert_dialog>

To open the dialog, use the show_modal/1 function.

<.button
  phx-click={Doggo.show_modal("end-session-modal")}
  aria-haspopup="dialog"
>
  show
</.button>

With HTML attributes

command and commandfor are the Invoker Commands API. Unlike the other two ways, this API needs no JavaScript at all.

<.button command="show-modal" commandfor="end-session-modal">show</.button>

Both attributes are recent, so the hook handles them if the browser doesn't support them.

Closing

The alert dialog can be closed by:

  • using hide_modal/1,
  • using JS.exec("data-cancel", to: "#end-session-modal"), which is what the example above uses for its own control, or
  • using the close button or Esc (only if dismissable is set).

Semantics

The dialog is opened with showModal(), so the browser puts it in the top layer, draws ::backdrop, makes the rest of the document inert and keeps the focus inside. aria-modal is not rendered, because showModal() already marks the component as a modal.

Focus

showModal() moves the focus into the dialog to the first element with the autofocus attribute, or the first focusable element if no element has it.

The alert dialog is not dismissable by default, so the first focusable element is usually the first control in the :footer slot. In a dismissable alert dialog, it is the close button. Neither is likely to be the right element to focus.

Set autofocus on the element that should receive the focus:

<:footer>
  <.button phx-click="end-session">Yes, end session</.button>
  <.button
    autofocus
    phx-click={JS.exec("data-cancel", to: "#end-session-modal")}
  >
    No, continue training
  </.button>
</:footer>

In an alert dialog, the focus should move to the least destructive action, as recommended in the ARIA Authoring Practices.

CSS

A dialog is hidden until it is opened, so no rule is needed for that. Style the backdrop with dialog.alert-dialog::backdrop.

Caveats

An alert dialog is not dismissable by default, so it renders closedby="none" and no close button, which leaves no way to dismiss it from the component. Provide your own control in the :footer slot.

Keyboard

  • Esc - close the dialog (only if dismissable is set).

An alert dialog is not dismissable by default, so it renders closedby="none" and no close button. Neither Esc nor a click outside closes it, which is the point: the answer has to come from the footer.

End Training Session Early?

Are you sure you want to end the current training session with Bella? She's making great progress today!

<div>
  <.button type="button" phx-click={Doggo.show_modal("alert-dialog-single-default")}>Open alert dialog</.button>
  <.alert_dialog id="alert-dialog-single-default">
    <:title>End Training Session Early?</:title>
    <p>
      Are you sure you want to end the current training session with Bella?
      She's making great progress today!
    </p>
    <:footer>
      <.button phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-default")}>
        Yes, end session
      </.button>
      <.button autofocus phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-default")}>
        No, continue training
      </.button>
    </:footer>
  </.alert_dialog>
</div>

dismissable adds the close button and closedby="any", so Esc and a click outside close it as well. Use it only when dismissing the dialog is itself a valid answer.

End Training Session Early?

Are you sure you want to end the current training session with Bella? She's making great progress today!

<div>
  <.button type="button" phx-click={Doggo.show_modal("alert-dialog-single-dismissable")}>Open alert dialog</.button>
  <.alert_dialog id="alert-dialog-single-dismissable" dismissable>
    <:title>End Training Session Early?</:title>
    <p>
      Are you sure you want to end the current training session with Bella?
      She's making great progress today!
    </p>
    <:footer>
      <.button phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-dismissable")}>
        Yes, end session
      </.button>
      <.button autofocus phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-dismissable")}>
        No, continue training
      </.button>
    </:footer>
  </.alert_dialog>
</div>

The :close slot replaces the label text of the close button with other content, usually an icon. close_label still gives the button its accessible name.

End Training Session Early?

Are you sure you want to end the current training session with Bella? She's making great progress today!

<div>
  <.button type="button" phx-click={Doggo.show_modal("alert-dialog-single-close-icon")}>Open alert dialog</.button>
  <.alert_dialog id="alert-dialog-single-close-icon" close_label="Close" dismissable>
    <:title>End Training Session Early?</:title>
    <p>
      Are you sure you want to end the current training session with Bella?
      She's making great progress today!
    </p>
    <:footer>
      <.button phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-close-icon")}>
        Yes, end session
      </.button>
      <.button autofocus phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-close-icon")}>
        No, continue training
      </.button>
    </:footer>
    <:close><svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="lucide lucide-x"
      aria-hidden="true"
    >
      <path d="M18 6 6 18" />
      <path d="m6 6 12 12" />
    </svg>
    </:close>
  </.alert_dialog>
</div>

The button has command and commandfor attributes, which are part of the Invoker Commands API. This works with only HTML attributes without any JavaScript. If the browser doesn't support it, the hook fills the functionality.

End Training Session Early?

Are you sure you want to end the current training session with Bella? She's making great progress today!

<div>
  <.button type="button" command="show-modal" commandfor="alert-dialog-single-without-javascript">Open alert dialog</.button>
  <.alert_dialog id="alert-dialog-single-without-javascript">
    <:title>End Training Session Early?</:title>
    <p>
      Are you sure you want to end the current training session with Bella?
      She's making great progress today!
    </p>
    <:footer>
      <.button phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-without-javascript")}>
        Yes, end session
      </.button>
      <.button autofocus phx-click={JS.exec("data-cancel", to: "#alert-dialog-single-without-javascript")}>
        No, continue training
      </.button>
    </:footer>
  </.alert_dialog>
</div>