AlertDialog

AlertDialog is a modal dialog component that interrupts the user with important content and expects a response. It is built on top of the Dialog component with opinionated defaults and a simplified API.

Install

#Differences from Dialog

AlertDialog provides these defaults on top of Dialog:

  • Not overlay closable by default (can be changed with overlay_closable(true))
  • No close button by default (can be changed with close_button(true))
  • Footer buttons are center-aligned (Dialog uses right-alignment)
  • Simplified API focused on alert and confirmation scenarios

#Import

use gpui_kit::component::dialog::{AlertDialog, DialogAction, DialogClose};
use gpui_kit::component::WindowExt;
// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { AlertDialog } from "alert-dialog.slint";
use iced::keyboard;
use iced::widget::{Stack, column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Element, Length, Subscription, Task, Theme};

#Usage

#Default

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The default confirmation: a title, a description and a centered footer
// whose cancel action comes first and whose destructive one comes last.
let default = body_box(
    column![
        open_button("Discard Draft", slot::DEFAULT),
        muted_line("A centered footer: Cancel first, then the destructive action."),
    ]
    .spacing(8.0),
);
let default_dialog = Dialog::new()
    .open(app.open[slot::DEFAULT])
    .close_button(false)
    .title("Discard unsaved changes?")
    .description("Your edits since the last save will be permanently lost.")
    .footer(
        DialogFooter::new()
            .centered(true)
            .child(close_button("Cancel", slot::DEFAULT))
            .child(
                Button::new("Discard")
                    .variant(button::Variant::Danger)
                    .on_press(Message::Opened {
                        slot: slot::DEFAULT,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::DEFAULT,
        open,
    });

Like Dialog, you need to set up your application’s root view to render the dialog layer. See Dialog documentation for details.

AlertDialog::new(cx)
    .trigger(Button::new("info-alert").outline().label("Discard Draft"))
    .on_ok(|_, window, cx| {
        window.push_notification("Draft discarded", cx);
        true
    })
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Discard unsaved changes?"))
                    .child(DialogDescription::new().child(
                        "Your edits since the last save will be permanently lost.",
                    )),
            )
            .child(
                DialogFooter::new()
                    .child(DialogClose::new().child(
                        Button::new("cancel").outline().label("Cancel"),
                    ))
                    .child(DialogAction::new().child(
                        Button::new("ok").label("Discard").danger(),
                    )),
            )
    })
row: root.row-of(0);
col: root.col-of(0);
Preview {
    label: "Discard Draft";
    clicked => {
        root.discard-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.discard-open;
        show-close: false;
        title: "Discard unsaved changes?";
        description: "Your edits since the last save will be permanently lost.";
        DialogFooter {
            centered: true;
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.discard-open = false;
                }
            }
            Button {
                text: "Discard";
                variant: ButtonVariant.danger;
                clicked => {
                    root.discard-open = false;
                }
            }
        }
    }
}

#Delete file

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The panel's body is the caller's, so a leading danger glyph and the two
// lines beside it are an ordinary row placed in `content`.
let delete = body_box(
    column![
        open_button("Delete File", slot::DELETE),
        muted_line("A danger glyph leads the title and the line beside it."),
    ]
    .spacing(8.0),
);
let delete_dialog = Dialog::new()
    .open(app.open[slot::DELETE])
    .close_button(false)
    .content(icon_row(
        INFO_XML,
        theme::Status::Danger,
        20.0,
        "Delete File",
        "Are you sure you want to delete this file? This action cannot be undone.",
    ))
    .footer(
        DialogFooter::new()
            .centered(true)
            .child(close_button("Cancel", slot::DELETE))
            .child(
                Button::new("Delete")
                    .variant(button::Variant::Danger)
                    .on_press(Message::Opened {
                        slot: slot::DELETE,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::DELETE,
        open,
    });

Using imperative API:

Button::new("delete")
    .danger()
    .label("Delete")
    .on_click(|_, window, cx| {
        window.open_alert_dialog(cx, |alert, _, _| {
            alert
                .title("Delete File?")
                .description("This action cannot be undone.")
                .confirm()
                .ok_text("Delete")
                .ok_variant(ButtonVariant::Danger)
                .on_ok(|_, window, cx| {
                    // Perform delete operation
                    window.push_notification("File deleted", cx);
                    true
                })
        });
    })
row: root.row-of(1);
col: root.col-of(1);
Preview {
    label: "Delete File";
    clicked => {
        root.delete-file-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.delete-file-open;
        show-close: false;
        HorizontalLayout {
            spacing: 12px;
            Icon {
                name: "info";
                size: 20px;
                color: Theme.danger;
            }
            VerticalLayout {
                spacing: 4px;
                horizontal-stretch: 1;
                Text {
                    text: "Delete File";
                    color: Theme.foreground;
                    font-size: Theme.font-size-lg;
                    font-weight: 600;
                }
                Text {
                    text: "Are you sure you want to delete this file? This action cannot be undone.";
                    color: Theme.muted-foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
            }
        }
        DialogFooter {
            centered: true;
            Button {
                text: "Cancel";
                clicked => {
                    root.delete-file-open = false;
                }
            }
            Button {
                text: "Delete";
                variant: ButtonVariant.danger;
                clicked => {
                    root.delete-file-open = false;
                }
            }
        }
    }
}

Or using declarative API with DialogAction/DialogClose:

AlertDialog::new(cx)
    .trigger(Button::new("delete").danger().label("Delete"))
    .on_ok(|_, window, cx| {
        window.push_notification("File deleted", cx);
        true
    })
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Delete File?"))
                    .child(DialogDescription::new().child("This action cannot be undone."))
            )
            .child(
                DialogFooter::new()
                    .child(
                        DialogClose::new().child(
                            Button::new("cancel").outline().label("Cancel")
                        )
                    )
                    .child(
                        DialogAction::new().child(
                            Button::new("delete-confirm").danger().label("Delete")
                        )
                    )
            )
    })
SlintNo Slint version of this example yet.

#Icon

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The centered prompt: the glyph, the title and the line stacked, and the
// two full-width actions stacked under them. A `footer` accepts any element,
// so it is a column here rather than the usual row.
let icon = body_box(
    column![
        open_button("Request Permission", slot::ICON),
        muted_line("Everything is centered, and the actions are stacked full width."),
    ]
    .spacing(8.0),
);
let icon_dialog =
    Dialog::new()
        .open(app.open[slot::ICON])
        .close_button(false)
        .content(
            column![
            container(
                svg(svg::Handle::from_memory(TRIANGLE_ALERT))
                    .width(40.0)
                    .height(40.0)
                    .style(|theme: &Theme, _status| svg::Style {
                        color: Some(Tokens::of(theme).warning),
                    })
            )
            .width(Length::Fill)
            .center_x(Length::Fill),
            container(
                text("Network Permission Required")
                    .size(theme::FONT_SIZE_LG)
                    .font(iced::Font {
                        weight: iced::font::Weight::Semibold,
                        ..iced::Font::DEFAULT
                    })
            )
            .width(Length::Fill)
            .center_x(Length::Fill),
            container(
                text("We need your permission to access the network. Allow access to continue.")
                    .size(theme::FONT_SIZE_SM)
                    .style(|theme: &Theme| text::Style {
                        color: Some(Tokens::of(theme).muted_foreground),
                    })
            )
            .width(Length::Fill)
            .center_x(Length::Fill),
        ]
            .spacing(8.0),
        )
        .footer(
            DialogFooter::new()
                .centered(true)
                .child(close_button("Cancel", slot::DELETE))
                .child(Button::new("Delete").on_press(Message::Opened {
                    slot: slot::DELETE,
                    open: false,
                })),
        )
        .on_open_change(|open| Message::Opened {
            slot: slot::ICON,
            open,
        });

Using icon in declarative API:

use gpui_kit::component::{Icon, IconName, ActiveTheme};

AlertDialog::new(cx)
    .w(px(320.))
    .trigger(Button::new("permission").outline().label("Request Permission"))
    .on_ok(|_, window, cx| {
        window.push_notification("Permission granted", cx);
        true
    })
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .items_center()
                    .child(
                        Icon::new(IconName::TriangleAlert)
                            .size_10()
                            .text_color(cx.theme().warning)
                    )
                    .child(DialogTitle::new().child("Network Permission Required"))
                    .child(DialogDescription::new().child(
                        "We need your permission to access the network to provide better services."
                    ))
            )
            .child(
                DialogFooter::new()
                    .v_flex()
                    .child(
                        DialogAction::new().child(
                            Button::new("allow").w_full().primary().label("Allow")
                        )
                    )
                    .child(
                        DialogClose::new().child(
                            Button::new("deny").w_full().outline().label("Don't Allow")
                        )
                    )
            )
    })
row: root.row-of(2);
col: root.col-of(2);
Preview {
    label: "Request Permission";
    clicked => {
        root.permission-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.permission-open;
        show-close: false;
        VerticalLayout {
            alignment: center;
            spacing: 8px;
            Icon {
                name: "triangle-alert";
                size: 40px;
                color: Theme.warning;
            }
            Text {
                text: "Network Permission Required";
                color: Theme.foreground;
                font-size: Theme.font-size-lg;
                font-weight: 600;
                horizontal-alignment: center;
                wrap: word-wrap;
            }
            Text {
                text: "We need your permission to access the network. Allow access to continue.";
                color: Theme.muted-foreground;
                font-size: Theme.font-size-sm;
                horizontal-alignment: center;
                wrap: word-wrap;
            }
        }
        VerticalLayout {
            spacing: 8px;
            Button {
                text: "Allow";
                variant: ButtonVariant.primary;
                horizontal-stretch: 1;
                clicked => {
                    root.permission-open = false;
                }
            }
            Button {
                text: "Deny";
                outline: true;
                horizontal-stretch: 1;
                clicked => {
                    root.permission-open = false;
                }
            }
        }
    }
}

Using icon in imperative API:

window.open_alert_dialog(cx, |alert, _, cx| {
    alert
        .title("Warning")
        .description("This action requires confirmation.")
        .icon(
            Icon::new(IconName::AlertTriangle)
                .size_8()
                .text_color(cx.theme().warning)
        )
})
SlintNo Slint version of this example yet.

#Destructive

AlertDialog::new(cx)
    .trigger(
        Button::new("delete-account")
            .outline()
            .danger()
            .label("Delete Account")
    )
    .on_ok(|_, window, cx| {
        window.push_notification("Account deletion initiated", cx);
        true
    })
    .content(|content, _, _| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Delete Account"))
                    .child(DialogDescription::new().child(
                        "This will permanently delete your account \
                        and all associated data. This action cannot be undone."
                    ))
            )
            .child(
                DialogFooter::new()
                    .child(
                        DialogClose::new().child(
                            Button::new("cancel").flex_1().outline().label("Cancel")
                        )
                    )
                    .child(
                        DialogAction::new().child(
                            Button::new("delete")
                                .flex_1()
                                .outline()
                                .danger()
                                .label("Delete Forever")
                        )
                    )
            )
    })
row: root.row-of(3);
col: root.col-of(3);
Preview {
    label: "Delete Account";
    variant: ButtonVariant.danger;
    clicked => {
        root.account-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.account-open;
        show-close: false;
        title: "Delete Account";
        description: "This will permanently delete your account and all associated data. This action cannot be undone.";
        DialogFooter {
            centered: true;
            Button {
                text: "Cancel";
                outline: true;
                horizontal-stretch: 1;
                clicked => {
                    root.account-open = false;
                }
            }
            Button {
                text: "Delete";
                outline: true;
                variant: ButtonVariant.danger;
                horizontal-stretch: 1;
                clicked => {
                    root.account-open = false;
                }
            }
        }
    }
}
// Both actions are outlined and share the row, which is the Slint example's
// `horizontal-stretch` on each button.
let destructive = body_box(
    column![
        open_button("Delete Account", slot::DESTRUCTIVE),
        muted_line("Two outlined actions share the row."),
    ]
    .spacing(8.0),
);
let destructive_dialog = Dialog::new()
    .open(app.open[slot::DESTRUCTIVE])
    .close_button(false)
    .title("Delete Account")
    .description(
        "This will permanently delete your account and all associated data. \
         This action cannot be undone.",
    )
    .footer(
        DialogFooter::new()
            .centered(true)
            .child(
                Button::new("Cancel")
                    .outline()
                    .width(Length::Fill)
                    .on_press(Message::Opened {
                        slot: slot::DESTRUCTIVE,
                        open: false,
                    }),
            )
            .child(
                Button::new("Delete")
                    .outline()
                    .variant(button::Variant::Danger)
                    .width(Length::Fill)
                    .on_press(Message::Opened {
                        slot: slot::DESTRUCTIVE,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::DESTRUCTIVE,
        open,
    });

#Without title

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The component itself: `AlertDialog` builds the centered footer from its
// two labels, and an empty title simply draws no title.
let untitled = body_box(
    column![
        open_button("Continue without Title", slot::UNTITLED),
        muted_line("`AlertDialog` with a description and no title."),
    ]
    .spacing(8.0),
);
let untitled_dialog = AlertDialog::new()
    .open(app.open[slot::UNTITLED])
    .description("Continue with this action?")
    .confirm_text("Continue")
    .on_confirm(Message::Opened {
        slot: slot::UNTITLED,
        open: false,
    })
    .on_open_change(|open| Message::Opened {
        slot: slot::UNTITLED,
        open,
    });

Create a fully declarative AlertDialog using trigger and content:

use gpui_kit::component::dialog::{AlertDialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter};

AlertDialog::new(cx)
    .trigger(
        Button::new("show-alert")
            .outline()
            .label("Show Alert")
    )
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Are you absolutely sure?"))
                    .child(DialogDescription::new().child(
                        "This action cannot be undone. \
                        This will permanently delete your account from our servers."
                    ))
            )
            .child(
                DialogFooter::new()
                    .child(
                        Button::new("cancel")
                            .outline()
                            .label("Cancel")
                            .on_click(|_, window, cx| {
                                window.close_dialog(cx);
                            })
                    )
                    .child(
                        Button::new("ok")
                            .primary()
                            .label("Continue")
                            .on_click(|_, window, cx| {
                                window.push_notification("Confirmed", cx);
                                window.close_dialog(cx);
                            })
                    )
            )
    })
row: root.row-of(4);
col: root.col-of(4);
Preview {
    label: "Continue without Title";
    clicked => {
        root.untitled-open = true;
    }
    AlertDialog {
        width: 100%;
        height: 100%;
        open <=> root.untitled-open;
        description: "Continue with this action?";
        confirm-text: "Continue";
    }
}
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// A single full-width action where the footer would be: the panel's body is
// the caller's, so the "footer" is just the last child.
let footer = body_box(
    column![
        open_button("Show Session Expiry", slot::FOOTER),
        muted_line("A single full-width action stands in for the footer."),
    ]
    .spacing(8.0),
);
let footer_dialog = Dialog::new()
    .open(app.open[slot::FOOTER])
    .close_button(false)
    .title("Session Expired")
    .description("Your session has expired due to inactivity. Please log in again to continue.")
    .content(
        Button::new("Sign in")
            .variant(button::Variant::Primary)
            .width(Length::Fill)
            .on_press(Message::Opened {
                slot: slot::FOOTER,
                open: false,
            }),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::FOOTER,
        open,
    });

Set the button text and variant directly on the dialog:

use gpui_kit::component::button::ButtonVariant;

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Delete Account")
        .description("This will permanently delete your account and all associated data.")
        .confirm()
        .ok_text("Delete")
        .ok_variant(ButtonVariant::Danger)
        .cancel_text("Keep")
        .on_ok(|_, window, cx| {
            window.push_notification("Account deleted", cx);
            true
        })
})
row: root.row-of(5);
col: root.col-of(5);
Preview {
    label: "Show Session Expiry";
    clicked => {
        root.session-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.session-open;
        show-close: false;
        title: "Session Expired";
        description: "Your session has expired due to inactivity. Please log in again to continue.";
        Button {
            text: "Sign in";
            variant: ButtonVariant.primary;
            horizontal-stretch: 1;
            clicked => {
                root.session-open = false;
            }
        }
    }
}

button_props takes the same properties as one value, for a configuration you want to build up or pass around. It overrides only the fields the value sets, so everything the dialog already carries — the Cancel button confirm asked for, a callback an earlier on_ok installed — survives, whatever the call order:

use gpui_kit::component::dialog::DialogButtonProps;

window.open_alert_dialog(cx, move |alert, _, _| {
    alert
        .title("Delete Account")
        .confirm()
        .button_props(
            DialogButtonProps::default()
                .ok_text("Delete")
                .ok_variant(ButtonVariant::Danger)
        )
})
SlintNo Slint version of this example yet.

#Custom content

AlertDialog::new(cx)
    .width(px(500.))
    .trigger(Button::new("custom-width").label("Custom Width"))
    .content(|content, _, _| {
        // ... dialog content
    })
row: root.row-of(6);
col: root.col-of(6);
Preview {
    label: "Install Update";
    clicked => {
        root.update-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.update-open;
        show-close: false;
        title: "Update Available";
        description: "A new version (v2.0.0) is available. This update includes new features and bug fixes.";
        Rectangle {
            background: Theme.muted;
            border-radius: Theme.radius-md;
            HorizontalLayout {
                padding: 12px;
                spacing: 8px;
                Button {
                    text: "Later";
                    outline: true;
                    horizontal-stretch: 1;
                    clicked => {
                        root.update-open = false;
                    }
                }
                Button {
                    text: "Update";
                    variant: ButtonVariant.primary;
                    horizontal-stretch: 1;
                    clicked => {
                        root.update-open = false;
                    }
                }
            }
        }
    }
}
// A muted trough holding the two actions, which is the Slint example's
// `Rectangle { background: Theme.muted }` around a row.
let content = body_box(
    column![
        open_button("Install Update", slot::CONTENT),
        muted_line("A muted trough holds the two actions."),
    ]
    .spacing(8.0),
);
let content_dialog = Dialog::new()
    .open(app.open[slot::CONTENT])
    .close_button(false)
    .title("Update Available")
    .description(
        "A new version (v2.0.0) is available. This update includes new features \
         and bug fixes.",
    )
    .content(
        container(
            row![
                Button::new("Later")
                    .outline()
                    .width(Length::Fill)
                    .on_press(Message::Opened {
                        slot: slot::CONTENT,
                        open: false,
                    }),
                Button::new("Update")
                    .variant(button::Variant::Primary)
                    .width(Length::Fill)
                    .on_press(Message::Opened {
                        slot: slot::CONTENT,
                        open: false,
                    }),
            ]
            .spacing(8.0),
        )
        .width(Length::Fill)
        .padding(12.0)
        .style(|theme: &Theme| container::Style {
            background: Some(Tokens::of(theme).muted.into()),
            border: iced::border::rounded(theme::RADIUS_MD),
            ..container::Style::default()
        }),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::CONTENT,
        open,
    });

#Notice

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// An acknowledgement: one full-width action in the centered footer.
let notice = body_box(
    column![
        open_button("Review Notice", slot::NOTICE),
        muted_line("An acknowledgement with one action to dismiss it."),
    ]
    .spacing(8.0),
);
let notice_dialog = Dialog::new()
    .open(app.open[slot::NOTICE])
    .close_button(false)
    .title("Important Notice")
    .description("Please read this important notice carefully before proceeding.")
    .footer(
        DialogFooter::new().centered(true).child(
            Button::new("Got It")
                .variant(button::Variant::Primary)
                .on_press(Message::Opened {
                    slot: slot::NOTICE,
                    open: false,
                }),
        ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::NOTICE,
        open,
    });

DialogAction and DialogClose are wrapper components that simplify button click handling by automatically triggering the appropriate actions:

  • DialogClose: Wraps a button to trigger the Cancel action, invoking on_cancel callback
  • DialogAction: Wraps a button to trigger the Confirm action, invoking on_ok callback

These components eliminate the need to manually call window.close_dialog(cx):

AlertDialog::new(cx)
    .trigger(Button::new("show-alert").outline().label("Show Alert"))
    .on_ok(|_, window, cx| {
        window.push_notification("You confirmed!", cx);
        true  // Return true to close dialog
    })
    .on_cancel(|_, window, cx| {
        window.push_notification("You cancelled!", cx);
        true
    })
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Confirm Action"))
                    .child(DialogDescription::new().child("Do you want to proceed?"))
            )
            .child(
                DialogFooter::new()
                    .child(
                        DialogClose::new().child(
                            Button::new("cancel").outline().label("Cancel")
                        )
                    )
                    .child(
                        DialogAction::new().child(
                            Button::new("ok").primary().label("Confirm")
                        )
                    )
            )
    })
row: root.row-of(7);
col: root.col-of(7);
Preview {
    label: "Review Notice";
    clicked => {
        root.notice-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.notice-open;
        show-close: false;
        title: "Important Notice";
        description: "Please read this important notice carefully before proceeding.";
        DialogFooter {
            centered: true;
            Button {
                text: "Got It";
                variant: ButtonVariant.primary;
                clicked => {
                    root.notice-open = false;
                }
            }
        }
    }
}

Benefits:

  • No need to manually close the dialog
  • Automatically connects to on_ok and on_cancel callbacks
  • Cleaner, more declarative code
  • Supports returning false from callbacks to prevent closing

#Confirm

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// `AlertDialog` again, this time with the title and the primary confirm
// label the caller chose.
let confirm = body_box(
    column![
        open_button("Open Confirmation", slot::CONFIRM),
        muted_line("`AlertDialog` with a title and a confirm label."),
    ]
    .spacing(8.0),
);
let confirm_dialog = AlertDialog::new()
    .open(app.open[slot::CONFIRM])
    .title("Are you sure?")
    .description("Continue with this action?")
    .confirm_text("OK")
    .on_confirm(Message::Opened {
        slot: slot::CONFIRM,
        open: false,
    })
    .on_open_change(|open| Message::Opened {
        slot: slot::CONFIRM,
        open,
    });

Open a dialog imperatively using WindowExt::open_alert_dialog:

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Delete File")
        .description("Are you sure you want to delete this file? This action cannot be undone.")
        .show_cancel(true)
        .on_ok(|_, window, cx| {
            window.push_notification("File deleted", cx);
            true // Return true to close dialog
        })
})
row: root.row-of(8);
col: root.col-of(8);
Preview {
    label: "Open Confirmation";
    clicked => {
        root.confirm-open = true;
    }
    AlertDialog {
        width: 100%;
        height: 100%;
        open <=> root.confirm-open;
        title: "Are you sure?";
        description: "Continue with this action?";
        confirm-text: "OK";
    }
}

#Prevent close

GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The footer buttons write the dialog's line instead of closing it; only the
// close button and the backdrop ask the caller to close, which is what
// `on_open_change` reports.
let sync = body_box(
    column![
        open_button("Close During Sync", slot::SYNC),
        muted_line("Nothing in the footer closes it; its buttons write the line."),
    ]
    .spacing(8.0),
);
let sync_note = if app.sync_note.is_empty() {
    "Your changes are still syncing. The dialog stays open until syncing finishes.".to_string()
} else {
    app.sync_note.clone()
};
let sync_dialog = Dialog::new()
    .open(app.open[slot::SYNC])
    .title("Sync in progress")
    .description(sync_note)
    .footer(
        DialogFooter::new()
            .centered(true)
            .child(
                Button::new("Wait")
                    .on_press(Message::Note("Waiting for sync to finish.".to_string())),
            )
            .child(
                Button::new("Close")
                    .variant(button::Variant::Primary)
                    .on_press(Message::Note("Cannot close while syncing.".to_string())),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::SYNC,
        open,
    });

#Allow Overlay Click to Close

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Notice")
        .description("Click outside this dialog or press ESC to close it.")
        .overlay_closable(true)
})
SlintNo Slint version of this example yet.

#Disable Keyboard ESC to Close

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Important Notice")
        .description("Please read this carefully before proceeding.")
        .keyboard(false)
})
SlintNo Slint version of this example yet.

#Show Close Button

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Information")
        .description("Some information...")
        .close_button(true)
})
SlintNo Slint version of this example yet.

#Prevent Dialog from Closing

Return false from on_ok or on_cancel callbacks to prevent the dialog from closing:

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Processing")
        .description("A process is running. Click Continue to stop it or Cancel to keep waiting.")
        .confirm()
        .ok_text("Continue")
        .on_ok(|_, window, cx| {
            // Return false to prevent closing
            window.push_notification("Cannot close: Process still running", cx);
            false
        })
        .on_cancel(|_, window, cx| {
            window.push_notification("Waiting...", cx);
            false
        })
})
row: root.row-of(9);
col: root.col-of(9);
Preview {
    label: "Close During Sync";
    clicked => {
        root.sync-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.sync-open;
        title: "Sync in progress";
        description: root.sync-note == "" ? "Your changes are still syncing. The dialog stays open until syncing finishes." : root.sync-note;
        DialogFooter {
            centered: true;
            Button {
                text: "Wait";
                clicked => {
                    root.sync-note = "Waiting for sync to finish.";
                }
            }
            Button {
                text: "Close";
                variant: ButtonVariant.primary;
                clicked => {
                    root.sync-note = "Cannot close while syncing.";
                }
            }
        }
    }
}

#Dialog Close Callback

Use on_close to execute actions after the dialog closes (called after on_ok or on_cancel):

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .title("Confirm")
        .description("Are you sure?")
        .on_close(|_, window, cx| {
            window.push_notification("Dialog closed", cx);
        })
})
SlintNo Slint version of this example yet.

#Session Timeout

window.open_alert_dialog(cx, |alert, _, _| {
    alert
        .content(|content, _, _| {
            content
                .child(
                    DialogHeader::new()
                        .items_center()
                        .child(DialogTitle::new().child("Session Expired"))
                        .child(DialogDescription::new().child(
                            "Your session has expired due to inactivity. \
                            Please log in again to continue."
                        ))
                )
                .child(
                    DialogFooter::new()
                        .child(
                            Button::new("sign-in")
                                .label("Sign in")
                                .primary()
                                .flex_1()
                                .on_click(|_, window, cx| {
                                    window.push_notification("Redirecting to login...", cx);
                                    window.close_dialog(cx);
                                })
                        )
                )
        })
})
SlintNo Slint version of this example yet.

#Update Available

AlertDialog::new(cx)
    .trigger(Button::new("update").outline().label("Update Available"))
    .on_cancel(|_, window, cx| {
        window.push_notification("Update postponed", cx);
        true
    })
    .on_ok(|_, window, cx| {
        window.push_notification("Starting update...", cx);
        true
    })
    .content(|content, _, _| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Update Available"))
                    .child(DialogDescription::new().child(
                        "A new version (v2.0.0) is available. \
                        This update includes new features and bug fixes."
                    ))
            )
            .child(
                DialogFooter::new()
                    .child(
                        DialogClose::new().child(
                            Button::new("later").flex_1().outline().label("Later")
                        )
                    )
                    .child(
                        DialogAction::new().child(
                            Button::new("update-now").flex_1().primary().label("Update Now")
                        )
                    )
            )
    })
SlintNo Slint version of this example yet.

#Best Practices

  1. Choose the Right API: Use imperative API (open_alert_dialog) for simple confirmations; use declarative API (trigger + content) for complex layouts or integration with other components
  2. Use DialogAction and DialogClose: Prefer wrapping buttons with DialogAction and DialogClose over manual window.close_dialog() calls for cleaner, more declarative code
  3. Clarify Intent: Use appropriate button variants (e.g., ButtonVariant::Danger for delete operations) to communicate the importance of actions
  4. Provide Clear Descriptions: Ensure users understand the consequences of their actions, especially for destructive operations
  5. Use Icons Wisely: Icons can enhance attention for warnings and errors, but use them appropriately
  6. Prevent Closing Carefully: Only prevent dialog closing when user confirmation is truly necessary (e.g., a process is running)
  7. Maintain Consistency: Keep dialog button order and styles consistent throughout your application

#API Reference

#AlertDialog

MethodDescription
new(cx)Create a new AlertDialog
trigger(element)Set trigger element that opens the dialog when clicked
content(builder)Set dialog content using a builder function (declarative API)
title(title)Set dialog title (imperative API)
description(desc)Set dialog description (imperative API)
icon(icon)Set dialog icon (imperative API)
confirm()Show OK and Cancel buttons
ok_text(text)Set OK button text, default “OK”
ok_variant(variant)Set OK button variant, default Primary
cancel_text(text)Set cancel button text, default “Cancel”
cancel_variant(variant)Set cancel button variant
button_props(props)Override the button properties the value sets, keep the rest
show_cancel(bool)Show/hide cancel button, default false
width(px)Set dialog width, default 420px
overlay_closable(bool)Allow clicking overlay to close, default false
close_button(bool)Show/hide close button, default false
keyboard(bool)Support ESC key to close, default true
on_ok(callback)Set OK button callback, return true to close dialog
on_cancel(callback)Set cancel button callback, return true to close dialog
on_close(callback)Set callback after dialog closes

#DialogButtonProps

Every property is unset until a builder sets it, and an unset property keeps whatever the dialog already carries.

MethodDescription
ok_text(text)Set OK button text, default “OK”
cancel_text(text)Set cancel button text, default “Cancel”
ok_variant(variant)Set OK button variant
cancel_variant(variant)Set cancel button variant
show_cancel(bool)Show/hide cancel button
on_ok(callback)Set OK callback
on_cancel(callback)Set cancel callback

#DialogAction

A wrapper component that automatically triggers the Confirm action when its child element is clicked. This invokes the on_ok callback set on the AlertDialog.

Usage:

DialogAction::new().child(
    Button::new("ok").primary().label("Confirm")
)
SlintNo Slint version of this example yet.

Behavior:

  • Dispatches Confirm action on click
  • Invokes the on_ok callback
  • Dialog closes if callback returns true
  • Dialog stays open if callback returns false

#DialogClose

A wrapper component that automatically triggers the Cancel action when its child element is clicked. This invokes the on_cancel callback set on the AlertDialog.

Usage:

DialogClose::new().child(
    Button::new("cancel").outline().label("Cancel")
)
SlintNo Slint version of this example yet.

Behavior:

  • Dispatches Cancel action on click
  • Invokes the on_cancel callback
  • Dialog closes if callback returns true (or if no callback is set)
  • Dialog stays open if callback returns false

#AlertDialog

A confirmation dialog. Mirrors GPUI Component's `AlertDialog`.

NameKindTypeDefault
openin-outbool
titleinstring
descriptioninstring
cancel-textinstring"Cancel"
confirm-textinstring"Continue"
destructiveinbool
cancelledcallback()
confirmedcallback()