Dialog

Dialog component for creating dialogs, confirmations, and alerts. Supports overlay, keyboard shortcuts, and various customizations.

Install

#Import

use gpui_kit::component::dialog::DialogButtonProps;
use gpui_kit::component::WindowExt;
// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Placement, Dialog, DialogFooter } from "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

#Where dialogs render

The window’s Root automatically mounts and renders dialogs. Open the window with gpui_kit::open_window, or wrap the application view in Root::new. Application views do not render overlay layers themselves.

#Basic Dialog

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .title("Welcome")
        .child("This is a dialog dialog.")
})
row: root.row-of(0);
col: root.col-of(0);
Preview {
    label: "Open Dialog";
    clicked => {
        root.basic-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.basic-open;
        title: "Welcome";
        description: "This is a basic dialog with a title, a description, and footer actions.";
        DialogFooter {
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.basic-open = false;
                }
            }
            Button {
                text: "Continue";
                variant: ButtonVariant.primary;
                clicked => {
                    root.basic-open = false;
                }
            }
        }
    }
}
// The whole of a basic dialog: a title, a description and a footer. `open` is
// the caller's, `on_open_change` reports the close button and a press on the
// backdrop, and `DialogFooter` is the action row.
let basic = body_box(
    column![
        open_button("Open Dialog", slot::BASIC),
        muted_line(
            "A title, a description and two actions. The close button and a \
             click on the backdrop both ask the caller to close it."
        ),
    ]
    .spacing(8.0),
);
let basic_dialog = Dialog::new()
    .open(app.open[slot::BASIC])
    .title("Welcome")
    .description("This is a basic dialog with a title, a description, and footer actions.")
    .footer(
        DialogFooter::new()
            .child(Button::new("Cancel").outline().on_press(Message::Opened {
                slot: slot::BASIC,
                open: false,
            }))
            .child(
                Button::new("Continue")
                    .variant(button::Variant::Primary)
                    .on_press(Message::Opened {
                        slot: slot::BASIC,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::BASIC,
        open,
    });

#Form Dialog

let input = cx.new(|cx| InputState::new(window, cx));

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .title("User Information")
        .child(
            v_flex()
                .gap_3()
                .child("Please enter your details:")
                .child(Input::new(&input))
        )
        .footer(|_, _, _, _| {
            vec![
                Button::new("ok")
                    .primary()
                    .label("Submit")
                    .on_click(|_, window, cx| {
                        window.close_dialog(cx);
                    }),
                Button::new("cancel")
                    .label("Cancel")
                    .on_click(|_, window, cx| {
                        window.close_dialog(cx);
                    }),
            ]
        })
})
row: root.row-of(1);
col: root.col-of(1);
Preview {
    label: "Edit Profile";
    clicked => {
        root.form-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.form-open;
        title: "User Information";
        description: "Please enter your details.";
        VerticalLayout {
            spacing: 8px;
            Text {
                text: "Name";
                color: Theme.muted-foreground;
                font-size: Theme.font-size-xs;
            }
            Input {
                text <=> root.name;
                placeholder: "Enter your name";
            }
            Text {
                text: "Email";
                color: Theme.muted-foreground;
                font-size: Theme.font-size-xs;
            }
            Input {
                text <=> root.email;
                placeholder: "Enter your email";
            }
        }
        DialogFooter {
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.form-open = false;
                }
            }
            Button {
                text: "Submit";
                variant: ButtonVariant.primary;
                clicked => {
                    root.form-open = false;
                }
            }
        }
    }
}
// The body is the caller's, so the fields are ordinary `Input`s holding the
// application's own strings. A dialog does not own a value or a validation
// rule any more than the form does.
let form = body_box(
    column![
        open_button("Edit Profile", slot::FORM),
        muted_line("Two fields in the body; the values live in the application."),
    ]
    .spacing(8.0),
);
let form_dialog = Dialog::new()
    .open(app.open[slot::FORM])
    .title("User Information")
    .description("Please enter your details.")
    .content(
        column![
            muted_line("Name"),
            Input::new("dialog-name")
                .value(app.name.clone())
                .placeholder("Enter your name")
                .on_input(Message::Name),
            muted_line("Email"),
            Input::new("dialog-email")
                .value(app.email.clone())
                .placeholder("Enter your email")
                .on_input(Message::Email),
        ]
        .spacing(8.0),
    )
    .footer(
        DialogFooter::new()
            .child(Button::new("Cancel").outline().on_press(Message::Opened {
                slot: slot::FORM,
                open: false,
            }))
            .child(
                Button::new("Submit")
                    .variant(button::Variant::Primary)
                    .on_press(Message::Opened {
                        slot: slot::FORM,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::FORM,
        open,
    });

#Dialog with Icon

window.open_dialog(cx, |dialog, _, cx| {
    dialog
        .child(
            h_flex()
                .gap_3()
                .child(Icon::new(IconName::TriangleAlert)
                    .size_6()
                    .text_color(cx.theme().warning))
                .child("This action cannot be undone.")
        )
})
row: root.row-of(2);
col: root.col-of(2);
Preview {
    label: "Discard Draft";
    clicked => {
        root.icon-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.icon-open;
        HorizontalLayout {
            spacing: 12px;
            Icon {
                name: "triangle-alert";
                size: 20px;
                color: Theme.warning;
            }
            VerticalLayout {
                horizontal-stretch: 1;
                spacing: 4px;
                Text {
                    text: "Unsaved Changes";
                    color: Theme.foreground;
                    font-size: Theme.font-size-lg;
                    font-weight: 600;
                }
                Text {
                    text: "This action cannot be undone.";
                    color: Theme.muted-foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
            }
        }
        DialogFooter {
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.icon-open = false;
                }
            }
            Button {
                text: "Discard";
                variant: ButtonVariant.danger;
                clicked => {
                    root.icon-open = false;
                }
            }
        }
    }
}
// A dialog with no title property: the icon, the heading and the line are one
// row of `content`, which is the Slint example's `HorizontalLayout` inside the
// panel.
let icon_body = row![
    svg(svg::Handle::from_memory(TRIANGLE_ALERT))
        .width(20.0)
        .height(20.0)
        .style(|theme: &Theme, _status| svg::Style {
            color: Some(Tokens::of(theme).warning),
        }),
    column![
        text("Unsaved Changes")
            .size(theme::FONT_SIZE_LG)
            .font(iced::Font {
                weight: iced::font::Weight::Semibold,
                ..iced::Font::DEFAULT
            })
            .style(|theme: &Theme| text::Style {
                color: Some(Tokens::of(theme).foreground),
            }),
        text("This action cannot be undone.")
            .size(theme::FONT_SIZE_SM)
            .style(|theme: &Theme| text::Style {
                color: Some(Tokens::of(theme).muted_foreground),
            }),
    ]
    .spacing(4.0),
]
.spacing(12.0)
.align_y(Alignment::Start);

let icon = body_box(
    column![
        open_button("Discard Draft", slot::ICON),
        muted_line("The panel's content is a row: a warning glyph and the text."),
    ]
    .spacing(8.0),
);
let icon_dialog = Dialog::new()
    .open(app.open[slot::ICON])
    .content(icon_body)
    .footer(
        DialogFooter::new()
            .child(Button::new("Cancel").outline().on_press(Message::Opened {
                slot: slot::ICON,
                open: false,
            }))
            .child(
                Button::new("Discard")
                    .variant(button::Variant::Danger)
                    .on_press(Message::Opened {
                        slot: slot::ICON,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::ICON,
        open,
    });

#Scrollable Dialog

use gpui_kit::component::text::markdown;

window.open_dialog(cx, |dialog, window, cx| {
    dialog
        .h(px(450.))
        .title("Long Content")
        .child(markdown(long_markdown_text))
})
row: root.row-of(3);
col: root.col-of(3);
Preview {
    label: "Long Content";
    clicked => {
        root.scroll-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.scroll-open;
        title: "Long Content";
        description: "The body scrolls inside a fixed height.";
        Rectangle {
            height: 140px;
            Scrollable {
                Text {
                    text: "A dialog never grows past the frame. When the body is taller than the space between the header and the footer, it scrolls instead of pushing the actions off screen.";
                    color: Theme.foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
                Text {
                    text: "Keep the title and the action row visible while the rest of the content moves. This is how long documents, tables, and settings panels stay usable in a small dialog.";
                    color: Theme.muted-foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
                Text {
                    text: "The scrollbar appears on the trailing edge and stays thin until the pointer is over it.";
                    color: Theme.foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
                Text {
                    text: "Set an explicit height on the scrolling region so Slint does not copy the content height onto the viewport.";
                    color: Theme.muted-foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
                Text {
                    text: "Everything below the fold is reachable with the wheel, the trackpad, or the scrollbar.";
                    color: Theme.foreground;
                    font-size: Theme.font-size-sm;
                    wrap: word-wrap;
                }
            }
        }
        DialogFooter {
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.scroll-open = false;
                }
            }
            Button {
                text: "Confirm";
                variant: ButtonVariant.primary;
                clicked => {
                    root.scroll-open = false;
                }
            }
        }
    }
}
// A bounded `Scrollable` is the Slint example's fixed-height body. The panel
// never grows past the frame, so the title and the action row stay put while
// the body scrolls.
let long_body = Scrollable::new(
    column![
        text(
            "A dialog never grows past the frame. When the body is taller than \
             the space between the header and the footer, it scrolls instead of \
             pushing the actions off screen."
        )
        .size(theme::FONT_SIZE_SM),
        text(
            "Keep the title and the action row visible while the rest of the \
             content moves. This is how long documents, tables, and settings \
             panels stay usable in a small dialog."
        )
        .size(theme::FONT_SIZE_SM)
        .style(|theme: &Theme| text::Style {
            color: Some(Tokens::of(theme).muted_foreground),
        }),
        text(
            "Set an explicit height on the scrolling region so iced bounds the \
             viewport rather than copying the content height onto it."
        )
        .size(theme::FONT_SIZE_SM),
        text(
            "Everything below the fold is reachable with the wheel, the trackpad, \
             or the scrollbar."
        )
        .size(theme::FONT_SIZE_SM)
        .style(|theme: &Theme| text::Style {
            color: Some(Tokens::of(theme).muted_foreground),
        }),
    ]
    .spacing(8.0),
)
.height(Length::Fixed(140.0));

let scrolling = body_box(
    column![
        open_button("Long Content", slot::SCROLL),
        muted_line("A 140px scroll area holds the body between the header and the footer."),
    ]
    .spacing(8.0),
);
let scroll_dialog = Dialog::new()
    .open(app.open[slot::SCROLL])
    .title("Long Content")
    .description("The body scrolls inside a fixed height.")
    .content(long_body)
    .footer(
        DialogFooter::new()
            .child(Button::new("Cancel").outline().on_press(Message::Opened {
                slot: slot::SCROLL,
                open: false,
            }))
            .child(
                Button::new("Confirm")
                    .variant(button::Variant::Primary)
                    .on_press(Message::Opened {
                        slot: slot::SCROLL,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::SCROLL,
        open,
    });

A dialog never extends past the window. Its width is capped at the viewport minus a 16px margin on each side, and its height at the space between its top offset and a 16px bottom margin, so the title and footer stay visible while the body scrolls. w, max_w, h, and margin_top apply within those limits; a dialog that already fits keeps its requested size and default position.

#Dialog Options

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .title("Custom Dialog")
        .overlay(true)              // Show overlay (default: true)
        .overlay_closable(true)     // Click overlay to close (default: true)
        .keyboard(true)             // ESC to close (default: true)
        .close_button(false)        // Show close button (default: true)
        .child("Dialog content")
})
row: root.row-of(4);
col: root.col-of(4);
Preview {
    label: "Open Options Dialog";
    clicked => {
        root.options-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.options-open;
        show-close: false;
        overlay-closable: false;
        title: "Dialog Options";
        description: "This dialog hides the close button and ignores clicks on the overlay.";
        DialogFooter {
            Button {
                text: "Got It";
                variant: ButtonVariant.primary;
                clicked => {
                    root.options-open = false;
                }
            }
        }
    }
}
// `close_button(false)` removes the header's close control and
// `overlay_closable(false)` makes a press on the backdrop ask to stay open;
// the backdrop still captures it, so nothing behind the dialog is reachable.
let options = body_box(
    column![
        open_button("Open Options Dialog", slot::OPTIONS),
        muted_line("No close button, and the backdrop ignores a click."),
    ]
    .spacing(8.0),
);
let options_dialog = Dialog::new()
    .open(app.open[slot::OPTIONS])
    .close_button(false)
    .overlay_closable(false)
    .title("Dialog Options")
    .description("This dialog hides the close button and ignores clicks on the overlay.")
    .footer(
        DialogFooter::new().child(
            Button::new("Got It")
                .variant(button::Variant::Primary)
                .on_press(Message::Opened {
                    slot: slot::OPTIONS,
                    open: false,
                }),
        ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::OPTIONS,
        open,
    });

#Action Buttons

A Dialog puts its own buttons in the footer and has them dispatch Confirm or Cancel; on_ok and on_cancel decide what Enter and Esc do. For a confirmation with default buttons, use AlertDialog.

#Nested Dialogs

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .title("First Dialog")
        .child("This is the first dialog")
        .footer(|_, _, _, _| {
            vec![
                Button::new("open-another")
                    .label("Open Another Dialog")
                    .on_click(|_, window, cx| {
                        window.open_dialog(cx, |dialog, _, _| {
                            dialog
                                .title("Second Dialog")
                                .child("This is nested")
                        });
                    }),
            ]
        })
})
row: root.row-of(5);
col: root.col-of(5);
Preview {
    label: "Open First Dialog";
    clicked => {
        root.nested-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.nested-open;
        title: "First Dialog";
        description: "This dialog opens another dialog on top of it.";
        DialogFooter {
            Button {
                text: "Close";
                outline: true;
                clicked => {
                    root.nested-open = false;
                }
            }
            Button {
                text: "Open Another";
                variant: ButtonVariant.primary;
                clicked => {
                    root.nested-second-open = true;
                }
            }
        }
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.nested-second-open;
        title: "Second Dialog";
        description: "This is the nested dialog.";
        DialogFooter {
            Button {
                text: "Done";
                variant: ButtonVariant.primary;
                clicked => {
                    root.nested-second-open = false;
                    root.nested-open = false;
                }
            }
        }
    }
}
// Two dialogs in the same stack. The second is pushed above the first, and a
// stack answers its topmost layer only, so the first stays open underneath
// until Done closes both — which is what the Slint example's second dialog
// does.
let nested = body_box(
    column![
        open_button("Open First Dialog", slot::NESTED_FIRST),
        muted_line("The first opens a second on top of it."),
    ]
    .spacing(8.0),
);
let nested_first = Dialog::new()
    .open(app.open[slot::NESTED_FIRST])
    .title("First Dialog")
    .description("This dialog opens another dialog on top of it.")
    .footer(
        DialogFooter::new()
            .child(Button::new("Close").outline().on_press(Message::Opened {
                slot: slot::NESTED_FIRST,
                open: false,
            }))
            .child(
                Button::new("Open Another")
                    .variant(button::Variant::Primary)
                    .on_press(Message::Opened {
                        slot: slot::NESTED_SECOND,
                        open: true,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::NESTED_FIRST,
        open,
    });
let nested_second = Dialog::new()
    .open(app.open[slot::NESTED_SECOND])
    .title("Second Dialog")
    .description("This is the nested dialog.")
    .footer(
        DialogFooter::new().child(
            Button::new("Done")
                .variant(button::Variant::Primary)
                .on_press(Message::NestedDone),
        ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::NESTED_SECOND,
        open,
    });

#Custom Styling

window.open_dialog(cx, |dialog, _, cx| {
    dialog
        .rounded(cx.theme().radius_lg)
        .bg(cx.theme().cyan)
        .text_color(cx.theme().info_foreground)
        .title("Custom Style")
        .child("Styled dialog content")
})
row: root.row-of(6);
col: root.col-of(6);
Preview {
    label: "Custom Style";
    clicked => {
        root.style-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.style-open;
        panel-background: Theme.primary;
        panel-border-color: Theme.primary;
        title-color: Theme.primary-foreground;
        description-color: Theme.primary-foreground.with-alpha(0.8);
        title: "Custom Dialog";
        description: "Background, border, and text colors all come from properties.";
        DialogFooter {
            Button {
                text: "Cancel";
                outline: true;
                clicked => {
                    root.style-open = false;
                }
            }
            Button {
                text: "Confirm";
                variant: ButtonVariant.secondary;
                clicked => {
                    root.style-open = false;
                }
            }
        }
    }
}
// `style` takes a function of the theme, so the panel's fill, edge and text
// colours can all come from the tokens the Slint example's properties name.
let styled = body_box(
    column![
        open_button("Custom Style", slot::STYLE),
        muted_line("The panel fill, edge and text colours come from `Style`."),
    ]
    .spacing(8.0),
);
let styled_dialog = Dialog::new()
    .open(app.open[slot::STYLE])
    .title("Custom Dialog")
    .description("Background, border, and text colors all come from properties.")
    .style(|theme: &Theme| {
        let tokens = Tokens::of(theme);
        Style::resolve(tokens)
            .fill(tokens.primary)
            .border(tokens.primary, 1.0)
            .title_color(tokens.primary_foreground)
            .description_color(theme::alpha(tokens.primary_foreground, 0.8))
            .text_color(tokens.primary_foreground)
    })
    .footer(
        DialogFooter::new()
            .child(Button::new("Cancel").outline().on_press(Message::Opened {
                slot: slot::STYLE,
                open: false,
            }))
            .child(
                Button::new("Confirm")
                    .variant(button::Variant::Secondary)
                    .on_press(Message::Opened {
                        slot: slot::STYLE,
                        open: false,
                    }),
            ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::STYLE,
        open,
    });

#Custom Padding

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .p_3()                      // Custom padding
        .title("Custom Padding")
        .child("Dialog with custom spacing")
})
row: root.row-of(7);
col: root.col-of(7);
Preview {
    label: "Custom Padding";
    clicked => {
        root.padding-open = true;
    }
    Dialog {
        width: 100%;
        height: 100%;
        open <=> root.padding-open;
        content-padding: 12px;
        title: "Custom Padding";
        description: "The content inset is controlled by content-padding.";
        DialogFooter {
            Button {
                text: "Close";
                variant: ButtonVariant.primary;
                clicked => {
                    root.padding-open = false;
                }
            }
        }
    }
}
// `content_padding` is the Slint `content-padding`: one value for the panel's
// inset instead of a fixed default.
let padding = body_box(
    column![
        open_button("Custom Padding", slot::PADDING),
        muted_line("A 12px inset replaces the default 20px."),
    ]
    .spacing(8.0),
);
let padding_dialog = Dialog::new()
    .open(app.open[slot::PADDING])
    .content_padding(12.0)
    .title("Custom Padding")
    .description("The content inset is controlled by content-padding.")
    .footer(
        DialogFooter::new().child(
            Button::new("Close")
                .variant(button::Variant::Primary)
                .on_press(Message::Opened {
                    slot: slot::PADDING,
                    open: false,
                }),
        ),
    )
    .on_open_change(|open| Message::Opened {
        slot: slot::PADDING,
        open,
    });

#Close Dialog Programmatically

The close_dialog method can be used to close the active dialog from anywhere within the window context.

// Close top level active dialog.
window.close_dialog(cx);

// Close and perform action
Button::new("submit")
    .primary()
    .label("Submit")
    .on_click(|_, window, cx| {
        // Do something
        window.close_dialog(cx);
    })
SlintNo Slint version of this example yet.

#Declarative API

The Dialog component now supports a declarative API that provides a more React-like component composition pattern using dedicated header, title, description, and footer components.

#Import

use gpui_kit::component::dialog::{
    Dialog, DialogHeader, DialogTitle, DialogDescription, DialogFooter,
};
SlintNo Slint version of this example yet.

#Trigger-based Dialog

The trigger-based approach allows you to create a dialog that opens when a trigger element is clicked. The dialog is defined inline with the trigger.

Dialog::new(cx)
    .trigger(
        Button::new("open-dialog")
            .outline()
            .label("Open Dialog")
    )
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Account Created"))
                    .child(DialogDescription::new().child(
                        "Your account has been created successfully!",
                    ))
            )
            .child(
                DialogFooter::new()
                    .border_t_1()
                    .border_color(cx.theme().border)
                    .bg(cx.theme().muted)
                    .child(
                        Button::new("cancel")
                            .outline()
                            .label("Cancel")
                            .on_click(|_, window, cx| {
                                window.close_dialog(cx);
                            })
                    )
                    .child(
                        Button::new("ok")
                            .primary()
                            .label("Save Changes")
                    )
            )
    })
SlintNo Slint version of this example yet.

#Content Builder Pattern

Use the content builder pattern with window.open_dialog for more control over dialog creation:

window.open_dialog(cx, |dialog, _, _| {
    dialog
        .w(px(400.))
        .content(|content, _, _| {
            content
                .child(
                    DialogHeader::new()
                        .child(DialogTitle::new().child("Custom Width"))
                        .child(DialogDescription::new().child(
                            "This dialog has a custom width of 400px.",
                        ))
                )
                .child(div().child(
                    "Content area with custom width configuration."
                ))
                .child(
                    DialogFooter::new()
                        .justify_center()
                        .child(
                            Button::new("cancel")
                                .flex_1()
                                .outline()
                                .label("Cancel")
                                .on_click(|_, window, cx| {
                                    window.close_dialog(cx);
                                })
                        )
                        .child(
                            Button::new("done")
                                .flex_1()
                                .primary()
                                .label("Done")
                                .on_click(|_, window, cx| {
                                    window.close_dialog(cx);
                                })
                        )
                )
        })
})
SlintNo Slint version of this example yet.

#Declarative Components

#DialogHeader

Container for the dialog’s title and description section.

DialogHeader::new()
    .child(DialogTitle::new().child("Title"))
    .child(DialogDescription::new().child("Description"))
SlintNo Slint version of this example yet.

#DialogTitle

Displays the main title of the dialog with semantic styling.

DialogTitle::new()
    .child("Account Settings")
SlintNo Slint version of this example yet.

#DialogDescription

Displays descriptive text below the title with muted styling.

DialogDescription::new()
    .child("Update your account settings and preferences here.")
SlintNo Slint version of this example yet.

#DialogFooter

Container for action buttons and footer content. Automatically applies proper spacing and alignment.

DialogFooter::new()
    .bg(cx.theme().muted)
    .border_t_1()
    .border_color(cx.theme().border)
    .child(Button::new("cancel").outline().label("Cancel"))
    .child(Button::new("save").primary().label("Save"))
SlintNo Slint version of this example yet.

#Form Dialog with Declarative API

let name_input = cx.new(|cx| InputState::new(window, cx));
let email_input = cx.new(|cx| InputState::new(window, cx));

Dialog::new(cx)
    .trigger(Button::new("edit-profile").label("Edit Profile"))
    .content(|content, _, cx| {
        content
            .child(
                DialogHeader::new()
                    .child(DialogTitle::new().child("Edit Profile"))
                    .child(DialogDescription::new().child(
                        "Make changes to your profile here. Click save when done."
                    ))
            )
            .child(
                v_flex()
                    .gap_4()
                    .py_4()
                    .child(
                        v_flex()
                            .gap_2()
                            .child("Name")
                            .child(Input::new(&name_input).placeholder("Enter your name"))
                    )
                    .child(
                        v_flex()
                            .gap_2()
                            .child("Email")
                            .child(Input::new(&email_input).placeholder("Enter your email"))
                    )
            )
            .child(
                DialogFooter::new()
                    .child(Button::new("cancel").outline().label("Cancel"))
                    .child(Button::new("save").primary().label("Save Changes"))
            )
    })
SlintNo Slint version of this example yet.

Customize the footer appearance with background colors, borders, and alignment:

DialogFooter::new()
    .justify_center()        // Center align buttons
    .bg(cx.theme().muted)    // Background color
    .border_t_1()            // Top border
    .border_color(cx.theme().border)
    .child(Button::new("btn1").flex_1().label("Cancel"))
    .child(Button::new("btn2").flex_1().primary().label("Confirm"))
SlintNo Slint version of this example yet.

#DialogContent Container

The DialogContent component provides a flexible container for dialog body content:

use gpui_kit::component::dialog::DialogContent;

window.open_dialog(cx, |dialog, _, _| {
    dialog.content(|content, _, cx| {
        content
            .child(DialogHeader::new()
                .child(DialogTitle::new().child("Settings"))
                .child(DialogDescription::new().child("Configure your preferences"))
            )
            .child(
                div()
                    .py_4()
                    .child("Main content area")
            )
            .child(DialogFooter::new()
                .child(Button::new("close").label("Close"))
            )
    })
})
SlintNo Slint version of this example yet.

#API Reference - Declarative Components

#Dialog

MethodDescription
new(cx)Create a new Dialog (no longer requires window param)
trigger(element)Set trigger element that opens the dialog
content(builder)Set content using a builder function
w(px) / width(px)Set dialog width
max_w(px)Set maximum width
margin_top(px)Set top margin
overlay(bool)Show/hide overlay (default: true)
overlay_closable(bool)Allow closing by clicking overlay (default: true)
keyboard(bool)Allow closing with ESC key (default: true)
close_button(bool)Show/hide close button (default: true)

#DialogContent

Container for dialog body content. Automatically applies padding and flex layout.

DialogContent::new()
    .child(DialogHeader::new()...)
    .child(/* your content */)
    .child(DialogFooter::new()...)
SlintNo Slint version of this example yet.

#DialogHeader

Container for title and description. Automatically applies vertical flex layout with proper gap.

DialogHeader::new()
    .child(DialogTitle::new().child("Title"))
    .child(DialogDescription::new().child("Description"))
SlintNo Slint version of this example yet.

#DialogTitle

Displays the dialog title with semantic styling (font-semibold, proper line-height).

DialogTitle::new()
    .child("Dialog Title")
SlintNo Slint version of this example yet.

#DialogDescription

Displays descriptive text with muted foreground color and proper text sizing.

DialogDescription::new()
    .child("This is a description text that provides more context.")
SlintNo Slint version of this example yet.

#DialogFooter

Container for footer buttons with automatic spacing and alignment.

DialogFooter::new()
    .justify_end()  // Right align (default)
    .child(Button::new("btn1").label("Cancel"))
    .child(Button::new("btn2").primary().label("OK"))
SlintNo Slint version of this example yet.

#Placement

top, bottom, left, right

#Dialog

A modal panel. Mirrors GPUI Component's `Dialog`: overlay, title, description, content, footer actions, and a close button. Render inside a clipped preview so the overlay stays in the example frame.

NameKindTypeDefaultDescription
openin-outbool
titleinstring
descriptioninstring
show-closeinbooltrue
overlay-closableinbooltrueClicking the dimmed backdrop closes the dialog, like GPUI's `overlay_closable`.
panel-backgroundincolorTheme.popoverStyling overrides, mirroring GPUI's `bg`, `text_color`, `rounded` and `p_*`. The defaults keep the built-in popover look.
title-colorincolorTheme.foreground
description-colorincolorTheme.muted-foreground
panel-border-colorincolorTheme.border
radiusinlengthTheme.radius-lg
content-paddinginlength-1pxA negative value keeps the default inset, which narrows in a small frame.
closedcallback()

#DialogFooter

NameKindTypeDefault
centeredinbool

#Breaking Changes

#Dialog::new() Signature Change

The Dialog::new() constructor no longer requires a window parameter:

// Old API (deprecated)
Dialog::new(window, cx)

// New API
Dialog::new(cx)
SlintNo Slint version of this example yet.

#Content Builder Function

The .content() method now accepts a builder function instead of a pre-built DialogContent:

// Old approach (still works)
dialog.child(DialogHeader::new()...)

// New declarative API
dialog.content(|content, window, cx| {
    content
        .child(DialogHeader::new()...)
        .child(DialogFooter::new()...)
})
SlintNo Slint version of this example yet.

#Best Practices

  1. Use Declarative Components: Prefer DialogHeader, DialogTitle, DialogDescription, and DialogFooter for consistent styling
  2. Trigger-based for Simple Cases: Use the trigger pattern for straightforward dialogs that open from a button
  3. Builder Pattern for Complex Dialogs: Use window.open_dialog with content builder for dialogs requiring complex logic or state
  4. Semantic Structure: Always include DialogHeader with title and description for accessibility
  5. Consistent Footer: Use DialogFooter for all action buttons to maintain visual consistency
  6. Proper Sizing: Explicitly set dialog width when content requires specific dimensions