Skip to content

ConfirmModal

ConfirmModal is the ordinary confirmation dialog component. It creates an exclusive input layer internally and passes that same layer to the low-level Modal, so while the dialog is open, background lists, page shortcuts, and other lower handlers do not handle the same keys.

confirm modal demo

This recording is generated by docs/tapes/confirm-modal.tape. The example first moves the background list to Queued build, then opens a delete confirmation. While the dialog is open, pressing j once does not move the background selection further. It then uses Tab to focus Delete, presses Enter, and demonstrates canceling with n.

cargo run --example confirm_modal

In the background state, press j/k to move and d to open the dialog. Inside the dialog, press Tab, arrow keys, or BackTab to switch buttons, Enter to trigger the focused button, y to confirm directly, and n or Esc to cancel.

ConfirmModal(
    open: confirm_open.get(),
    title: Line::from("Delete release?"),
    content: format!("Remove {selected_label} from the queue?"),
    confirm_text: "Delete".to_string(),
    cancel_text: "Keep".to_string(),
    on_confirm: move |_: ()| {
        status.set(format!("deleted {selected_label}"));
        confirm_open.set(false);
    },
    on_cancel: move |_: ()| {
        status.set("kept release".to_string());
        confirm_open.set(false);
    },
)

ConfirmModal is controlled: open decides whether it is shown, and the component does not mutate external state by itself. on_confirm and on_cancel express only user intent; business code decides whether to close the dialog in the callback. That lets you run synchronous validation, async submit, or secondary state updates before closing.

Internally, the component is equivalent to this pair:

let layer = hooks.use_input_layer(open, true);

hooks.use_event_handler(EventScope::Layer(layer), EventPriority::High, move |event| {
    // handle Tab / Enter / y / n / Esc
    EventResult::Consumed
});

Modal(open: open, layer: Some(layer)) {
    // dialog content
}

This is the pattern recommended in Input Layers. Ordinary confirmation dialogs do not need business code to write use_input_layer; pass open, text, and callbacks to ConfirmModal, and it guarantees that the foreground dialog owns the keyboard.

Unknown keys are consumed by the dialog layer too. In the example, pressing j while the dialog is open does not move the background queue because the ConfirmModal layer has blocks_lower=true.

Focus defaults to the cancel button, which is the safer default for confirmation dialogs. Users can change or trigger buttons with:

KeyBehavior
Tab / BackTabSwitch between cancel and confirm
Left / RightSwitch between cancel and confirm
EnterTrigger the focused button
y / YConfirm directly
n / N / EscCancel directly

button_style styles unfocused buttons. selected_button_style styles the focused button: border and text highlight together, but the whole button body is not filled as a color block. If the style includes a background color, the component converts that background into the focus accent color:

ConfirmModal(
    button_style: Style::new().gray(),
    selected_button_style: Style::new().yellow().bold(),
)

ConfirmModal exposes the low-level Modal width, height, and mask style, plus content-area styles:

ConfirmModal(
    width: Constraint::Length(70),
    height: Constraint::Length(10),
    style: Style::new().dim(),
    border_style: Style::new().yellow(),
    title_style: Style::new().yellow().bold(),
    content_style: Style::new(),
)

If the dialog needs lists, input boxes, scroll regions, or complex shortcuts, use the low-level Modal + use_input_layer composition instead. For informational dialogs, see AlertModal; for shortcut help, see ShortcutInfoModal.