--- title: Lustre / Gleam: How to open confirm dialog date: 2025-10-13 10:34:19.663052 UTC --- Given that we have a list of items, and a "delete" button for each item. We want that when user clicks the button, a confirm dialog will appear, asking user one more time, before proceeding with API call to delete the item from the database. ![Delete confirmation](https://cdn.imgchest.com/files/91fb418bd950.png) Here is how to do that in [Lustre](https://hexdocs.pm/lustre/) (a frontend web framework in [Gleam](https://gleam.run/) language). The dialog in this example is the HTML native dialog created by [`window.confirm()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) method, in case we want some thing quick and simple. If you want to use [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog) element, I will write later. First, let's write a view function to prepare the HTML: ```gleam import lustre/attribute as a import lustre/element/html as h import lustre/event as ev import lucide_lustre as lucide_icon fn render_post_row(post: Post) { let Post(id:, ..) = post h.button( [ a.type_("button"), a.class("hover:text-red-600 cursor-pointer"), ], [ lucide_icon.eraser([a.class("w-5 h-auto")]), ], ), } ``` We define message variants for the click event and the "confirmed" event: ```gleam pub type Msg { ... UserClickedDeletion(String) UserConfirmedDeletion(String) } ``` The payload of these messages is a field of `String`, to carry the item ID. We need to keep this ID to pass to API call later, and then when go back to the UI, identify the row to remove. Attach the click handler: ```gleam h.button( [ ..., ev.on_click(UserClickedDeletion(id)), ], [ ... ], ), ``` When handling the `UserClickedDeletion` message, we will call the JS method `window.confirm` to create and show the dialog. It's JS code, so we need to create a simple FFI function: In _element.ffi.mjs_: ```js /** * Show a confirmation dialog to the user * @param {string} message - The message to display in the confirmation dialog * @returns {boolean} True if user confirmed, false otherwise */ export function confirm(message) { return window.confirm(message) } ``` In _ffi.gleam_: ```gleam @external(javascript, "./element.ffi.mjs", "confirm") pub fn confirm(message: String) -> Bool ``` The operation of showing dialog, waiting for user action must be asynchronous, to not block our app update cycle, so we will implement it as [`Effect`](https://hexdocs.pm/lustre/lustre/effect.html#Effect). The [`effect.before_paint()`](https://hexdocs.pm/lustre/lustre/effect.html#before_paint) utility can be used for this: ```gleam fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { UserClickedDeletion(id) -> { // To show dialog for deletion confirmation let whatnext = { use dispatch, _root <- effect.before_paint let agreed = ffi.confirm("Are you sure want to delete?") io.println("User agree? " <> bool.to_string(agreed)) use <- bool.guard(!agreed, Nil) dispatch(UserConfirmedDeletion(id)) } #(model, whatnext) } } } ``` If user clicks "Yes", the Lustre runtime will send back the `UserConfirmedDeletion` message to us. If you are not familiar with Gleam `use` expression yet, here is the callback version: ```gleam let whatsnext = effect.before_paint(fn(dispatch, _root) { let agreed = ffi.confirm("Are you sure want to delete?") io.println("User agree? " <> bool.to_string(agreed)) case agreed { True -> dispatch(UserConfirmedDeletion(id)) False -> Nil } }) ``` In the handling of `UserConfirmedDeletion`, we start to call API to delete the item: ```gleam fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { UserConfirmedDeletion(id) -> { let model = Model(..model, loading_status: IsSubmitting) let whatnext = actions.delete_content_item_via_api(id) #(model, whatnext) } } } ``` I don't go to the detail of `actions.delete_content_item_via_api()`, because it is out of scope. Basically, it is just an `Effect` that uses [`rsvp`](https://hexdocs.pm/rsvp/index.html) to make API call.