Dialog
Dialog component for creating dialogs, confirmations, and alerts. Supports overlay, keyboard shortcuts, and various customizations.
#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);
})#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,
};#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")
)
)
})#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);
})
)
)
})
})#Declarative Components
#DialogHeader
Container for the dialog’s title and description section.
DialogHeader::new()
.child(DialogTitle::new().child("Title"))
.child(DialogDescription::new().child("Description"))#DialogTitle
Displays the main title of the dialog with semantic styling.
DialogTitle::new()
.child("Account Settings")#DialogDescription
Displays descriptive text below the title with muted styling.
DialogDescription::new()
.child("Update your account settings and preferences here.")#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"))#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"))
)
})#Styled Footer
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"))#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"))
)
})
})#API Reference - Declarative Components
#Dialog
| Method | Description |
|---|---|
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()...)#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"))#DialogTitle
Displays the dialog title with semantic styling (font-semibold, proper line-height).
DialogTitle::new()
.child("Dialog Title")#DialogDescription
Displays descriptive text with muted foreground color and proper text sizing.
DialogDescription::new()
.child("This is a description text that provides more context.")#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"))#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.
| Name | Kind | Type | Default | Description |
|---|---|---|---|---|
open | in-out | bool | ||
title | in | string | ||
description | in | string | ||
show-close | in | bool | true | |
overlay-closable | in | bool | true | Clicking the dimmed backdrop closes the dialog, like GPUI's `overlay_closable`. |
panel-background | in | color | Theme.popover | Styling overrides, mirroring GPUI's `bg`, `text_color`, `rounded` and `p_*`. The defaults keep the built-in popover look. |
title-color | in | color | Theme.foreground | |
description-color | in | color | Theme.muted-foreground | |
panel-border-color | in | color | Theme.border | |
radius | in | length | Theme.radius-lg | |
content-padding | in | length | -1px | A negative value keeps the default inset, which narrows in a small frame. |
closed | callback | () |
#DialogFooter
| Name | Kind | Type | Default |
|---|---|---|---|
centered | in | bool |
#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)#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()...)
})#Best Practices
- Use Declarative Components: Prefer
DialogHeader,DialogTitle,DialogDescription, andDialogFooterfor consistent styling - Trigger-based for Simple Cases: Use the trigger pattern for straightforward dialogs that open from a button
- Builder Pattern for Complex Dialogs: Use
window.open_dialogwith content builder for dialogs requiring complex logic or state - Semantic Structure: Always include
DialogHeaderwith title and description for accessibility - Consistent Footer: Use
DialogFooterfor all action buttons to maintain visual consistency - Proper Sizing: Explicitly set dialog width when content requires specific dimensions