Dialog
Dialog is a modal overlay: a centered, bordered box with a title, an optional description or custom content area, and an optional footer of interactive children. It wraps a View, so it has its own focus scope.
Focus Open Dialog and press Enter. Inside the dialog, Tab moves between buttons, Enter presses the focused button, Escape closes the overlay in the demo, and Alt+T opens the shared theme picker.
Modal State Is App-Owned
The library does not own open/closed state. Keep dialog state in your app state, usually as an Option.
use ratcn::{Button, Dialog};
use ratcn::view::{EventResult, FocusState};
#[derive(Default)]
struct DialogState {
focus: FocusState,
}
struct AppState {
dialog: Option<DialogState>,
theme: Theme,
}Render the base UI first, then render the dialog on top while open. Route events to the dialog first while it is open.
root.render(frame, frame.area(), &state);
if state.dialog.is_some() {
dialog.render(frame, frame.area(), &state);
}
if state.dialog.is_some() {
if let EventResult::Emit(msg) = dialog.handle_event(&event, &state) {
update(&mut state, msg);
}
return;
}
if let EventResult::Emit(msg) = root.handle_event(&event, &state) {
update(&mut state, msg);
}Building A Dialog
let dialog = Dialog::new("confirm")
.focus(
|s: &AppState| &s.dialog.as_ref().expect("open").focus,
Msg::DialogFocusChanged,
)
.theme(|s: &AppState| &s.theme)
.title("Delete item")
.description("This cannot be undone.")
.child("delete", Button::new("Delete").destructive().on_press(Msg::Delete))
.child("cancel", Button::new("Cancel").secondary().on_press(Msg::Cancel))
.footer(|frame, _state, view| {
view.render_child(frame, "delete", delete_area);
view.render_child(frame, "cancel", cancel_area);
});Dialog API
Dialog::new(id): Creates a dialog focus scope. Defaults toFocusPolicy::Wrap..title("..."): Sets the title shown in the top border..description("..."): Renders a wrapped text body and auto-sizes the dialog. Ignored when.content(...)is set..content(|frame, state, view| ...): Custom main content closure. Usesview.area()for the content strip..content_height(height): Preferred height for custom content..child(id, component): Registers an interactive child, usually footer buttons. Declaration order is Tab order..footer(|frame, state, view| ...): Custom footer closure. Usesview.area()for the footer strip..focus(read, on_change): Binds the dialog focus state to app state..theme(read): Reads the active theme from app state..focus_policy(policy): Overrides Tab wrap behavior..render(frame, area, state): Paints the dialog overlay..handle_event(event, state): Routes input to the dialog focus scope.
Events
Dialog delegates event handling to its inner View. While a modal is open, route events to the dialog before the base UI. Tab and Shift+Tab move within the dialog according to its focus policy, and child components emit their own messages. Escape is not built into Dialog; close it from app-level key handling or from a child message such as Cancel.
Description vs Content
Use .description(...) for simple confirmation or alert dialogs. Use .content(...) when the body needs arbitrary Ratatui widgets or focusable children. If both are provided, custom content wins.
Focus Behavior
Dialog uses a nested View, so footer buttons and any custom content children share one focus path. The default focus policy wraps inside the dialog, which is usually what a modal should do.