Skip to content

Overlays & Navigation Widgets

Overlay Z-Ordering

Overlays are rendered above the main content at the root level:

  1. Modal - lowest (backdrop covers entire screen)
  2. Popover - middle
  3. Toast - highest (always visible)

Centered dialog overlay. Portals to root level regardless of declaration location.

PropTypeDescription
titleimpl Into<String>Constructor - dialog title
childElementDialog content
scopeOverlayScopeRootPortal (default) or Local
on_closeCallback<()>Close callback (Esc/backdrop click)
widthLengthDialog width
heightLengthDialog height
backdrop_styleStyleBackdrop overlay style
frame_styleStyleDialog container style
border_styleBorderStyleDialog border
paddingimpl Into<Padding>Dialog inner padding
title_styleStyleTitle style
rust
if ctx.state.show_confirm {
    Modal::new("Confirm Delete")
        .child(
            VStack::new()
                .gap(1)
                .child(Text::new("This action cannot be undone."))
                .child(
                    HStack::new().gap(1)
                        .child(Button::new("Cancel")
                            .on_click(ctx.link().callback(|_| Msg::CancelDelete)))
                        .child(Button::new("Delete")
                            .style(Style::new().fg(Color::White).bg(Color::Red))
                            .on_click(ctx.link().callback(|_| Msg::ConfirmDelete)))
                )
        )
        .on_close(ctx.link().callback(|_| Msg::CancelDelete))
        .into()
}

Toast Notifications

Toasts use ctx.toast() - no view tree setup required.

App configuration:

rust
App::new()
    .toast_placement(ToastPlacement::BottomEnd)  // default
    .toast_gap(1)
    .mount(Root)
    .run()

Showing toasts:

rust
fn update(&mut self, msg: Msg, ctx: &mut Context<Self>) -> Update {
    match msg {
        Msg::SaveSuccess => {
            ctx.toast().push(Toast::new("Saved successfully!"));
            Update::full()
        }
        Msg::SaveError(e) => {
            ctx.toast().push(
                Toast::new(format!("Save failed: {e}"))
                    .title("Error")
                    .border(true)
            );
            Update::full()
        }
        Msg::ShowCustom => {
            let id = ctx.toast().push(
                Toast::new("Custom message")
                    .duration(10.0)
                    .border(true)
            );
            ctx.state.toast_id = Some(id);  // Store to dismiss later
            Update::full()
        }
        Msg::DismissToast => {
            if let Some(id) = ctx.state.toast_id.take() {
                ctx.toast().dismiss(id);
            }
            Update::none()
        }
    }
}

ToastHandle methods:

MethodDescription
.push(Toast)Show toast, returns OverlayId
.dismiss(id)Dismiss a specific toast
.clear()Clear all toasts

ToastPlacement: TopStart, TopCenter, TopEnd, BottomStart, BottomCenter, BottomEnd (default).

Toast props:

PropTypeDescription
messageimpl Into<String>Constructor - toast text
durationf32Auto-dismiss seconds (0 = permanent)
titleStringOptional title
title_prefixStringTitle prefix symbol
title_suffixStringTitle suffix
title_alignmentAlignTitle alignment
title_styleStyleTitle style
message_styleStyleMessage style
frame_styleStyleContainer style
borderboolShow border
border_styleBorderStyleBorder style
paddingimpl Into<Padding>Padding
widthLengthWidth
max_widthLengthMaximum width
wrapboolWrap long messages
decoration / decorationsFrameDecorationEdge decorations

Toasts are suppressed in inline mode to avoid terminal history corruption.


Popover

Floating content panel triggered by an element.

PropTypeDescription
triggerElementThe trigger element
contentElementPopover content
openboolControlled open state
scopeOverlayScopeRootPortal (default) or Local
on_closeCallback<()>Close callback
placementPopoverPlacementTop, Bottom, Left, Right + variants
offsetu16Distance from trigger
clampboolKeep within screen bounds
auto_flipboolFlip placement when out of bounds
match_trigger_widthboolMatch popover width to trigger
anchorPopoverAnchorAlignment relative to trigger

Popover renders through the root overlay pipeline by default, so it appears above normal in-tree content. Use .scope(OverlayScope::Local) when it should stay inside parent stacking order, such as an autocomplete attached to content that can be covered by an inline sidebar layer.


Tooltip

Help text on hover or focus.

PropTypeDescription
textimpl Into<String>Constructor - tooltip text
childElementThe element to add tooltip to
openboolControlled open state
autoboolAuto-show on hover/focus
text_styleStyleTooltip text style
container_styleStyleTooltip container style
borderboolShow border
border_styleBorderStyleBorder style
paddingimpl Into<Padding>Inner padding
placementPopoverPlacementTooltip placement
offsetu16Distance from element
clampboolKeep within screen bounds
auto_flipboolFlip when out of bounds
rust
Tooltip::new("This button saves your work")
    .auto(true)
    .placement(PopoverPlacement::Top)
    .child(
        Button::new("Save")
            .on_click(ctx.link().callback(|_| Msg::Save))
            .into()
    )

Accordion

Collapsible content sections.

PropTypeDescription
exclusiveboolOnly one section open at a time
gapu16Gap between sections
paddingimpl Into<Padding>Outer padding
borderboolSection border
border_styleBorderStyleSection border style
styleStyleContainer style
header_styleStyleHeader idle style
header_hover_styleStyleHeader hover style
header_focus_styleStyleHeader focus style
header_paddingimpl Into<Padding>Header padding
content_paddingimpl Into<Padding>Content area padding
content_borderboolContent area border
content_styleStyleContent area style
expanded_iconcharExpanded section icon
collapsed_iconcharCollapsed section icon
disabled_styleStyleStyle when disabled
focusableboolWhether headers participate in focus traversal
widthLengthWidth
heightLengthHeight
on_toggleCallback<AccordionEvent>Section toggle event
rust
Accordion::new()
    .exclusive(true)
    .item(AccordionItem::new(
        "Section 1",
        Text::new("Content for section 1").into()
    ))
    .item(AccordionItem::new(
        "Section 2",
        Text::new("Content for section 2").into()
    ))

SearchPalette

Fuzzy search widget powered by nucleo. Composes an Input and List into a filterable, keyboard-navigable search panel.

SearchPalette is not an overlay by itself - wrap it in Modal for the classic command-palette experience, or embed it inline in a Frame, sidebar, or any other container.


CommandPalette

CommandPalette is a composite overlay widget that reads commands from ctx.command_registry() and renders them through SearchPalette<CommandId> inside a Modal.

Use ctx.register_command(...) inside a component to register component-scoped commands, or ctx.command_registry().register(...) for app-wide commands.

PropTypeDescription
on_closeCallback<()>Fired when the modal closes or a command executes
show_disabledboolInclude disabled commands in results (muted and non-activating)
titleimpl Into<RichText>Modal title
widthLengthModal width
heightLengthModal height
scopeOverlayScopeRootPortal (default) or Local
backdrop_styleStyleBackdrop overlay style
frame_styleStyleModal frame style
borderboolShow modal border
border_styleBorderStyleModal border style
paddingimpl Into<Padding>Modal content padding
title_styleStyleTitle style
title_alignmentAlignTitle alignment
rust
fn init(&mut self, ctx: &mut Context<Self>) -> Option<Command> {
    let link = ctx.link().clone();
    ctx.register_command(
        CommandEntry::builder("app.toggle-wrap")
            .label("Toggle word wrap")
            .description("Enable or disable editor wrapping")
            .category("Application")
            .keybinding("p")
            .handler(Callback::new(move |_| link.send(Msg::ToggleWrap)))
            .build(),
    );
    None
}

fn view(&self, ctx: &Context<Self>) -> Element {
    if ctx.state.show_palette {
        CommandPalette::new()
            .title("Commands")
            .show_disabled(true)
            .on_close(ctx.link().callback(|_| Msg::ClosePalette))
            .into()
    } else {
        Element::empty()
    }
}

Command ids are open-ended (CommandId) and can be grouped with optional categories and right-aligned keybinding hints.

Items can be provided flat via .items() or grouped via .entries() using SearchEntry::item(...), SearchEntry::header(...), and SearchEntry::spacer().

Headers and spacers are display-only rows (not searchable/selectable). Group rendering rules:

  • With an empty query, all item rows are shown and headers/spacers render in entry order.
  • With a non-empty query, grouped chrome is hidden and matches render as a flat ranked list.

Item text is typically built from SearchItem::new(label, value) and optional .description("..."). By default, description renders inline as label - description and uses description_style.

You can also add hidden aliases with SearchItem::alias(...) or SearchItem::aliases(...). Aliases are searched and scored like the label, but they are never rendered, which makes them useful for abbreviations, legacy names, or alternate command titles.

You can also mark rows active with .active(true) on SearchItem or SearchEntry::item(...).

Core props

PropTypeDescription
itemsimpl IntoIterator<Item = SearchItem<T>>Flat searchable items (clears entries)
entriesimpl IntoIterator<Item = SearchEntry<T>>Grouped entries via item/header/spacer rows
sync_match_limitusizeMax item count that still matches synchronously (default: 100)
sync_selectionboolKeep on_select synced with the current visible row
initial_queryimpl Into<Arc<str>>Pre-populate search field
initial_selected_item_indexOption<usize>Start selection on this items index when it appears in results (else first row)
placeholderimpl Into<Arc<str>>Input placeholder (default: "Search...")
widthLengthRequested palette width (default: Flex(1))
heightLengthRequested palette height (default: Flex(1))
max_widthLengthMaximum palette width constraint
max_heightLengthMaximum palette height constraint

Callbacks

PropTypeDescription
on_query_changeCallback<Arc<str>>Fired when the query text changes
on_selectCallback<SearchEvent<T>>Fired when selection moves; with sync_selection(true) also fires for initial/result-driven selection
on_activateCallback<SearchEvent<T>>Fired on Enter or double-click

Input forwarding

PropTypeDescription
input_prefiximpl Into<Arc<str>>Prefix before query text (default: " ")
input_suffiximpl Into<Arc<str>>Suffix after query text (default: "{matches}/{total}")
input_borderboolShow input border
input_dividerboolRender divider below input (uncontrolled mode, default: true)
input_divider_styleStyleDivider style below input
input_divider_join_frameboolJoin divider with surrounding frame border (default: true)
input_caret_shapeCaretShapeInput caret shape (Block, Bar, Underline)
input_caret_colorColorInput caret color (OSC 12 cursor color, terminal support required)
input_border_styleBorderStyleInput border style
input_paddingimpl Into<Padding>Input padding
input_styleStyleInput base style
input_hover_styleStyleInput hover style
input_focus_styleStyleInput focus style
input_placeholder_styleStylePlaceholder style
input_focus_placeholder_styleStylePlaceholder style when focused
input_prefix_styleStylePrefix style
input_focus_prefix_styleStylePrefix style when focused
input_suffix_styleStyleSuffix style
input_focus_suffix_styleStyleSuffix style when focused

List forwarding

PropTypeDescription
list_borderboolShow list border
list_border_styleBorderStyleList border style
list_paddingimpl Into<Padding>List padding
list_styleStyleList base style
list_hover_styleStyleList hover style
list_selection_styleStyleSelected item style
list_selection_symbolimpl Into<Arc<str>>Selection indicator (default: "> ")
list_selection_symbol_styleStyleSelection indicator style
list_unselected_symbolimpl Into<Arc<str>>Non-selected item indent
list_selection_full_widthboolExtend selection to full width
list_item_hover_styleStyleIndividual item hover style
list_active_styleStyleActive item style
list_active_symbolimpl Into<Arc<str>>Active item symbol
list_active_symbol_styleStyleActive symbol style
list_item_horizontal_paddingimpl Into<Padding>Normal row padding (left/right used)
list_header_horizontal_paddingimpl Into<Padding>Header row padding (left/right used)
list_focusableboolAllow list keyboard focus (default: true)
list_scrollbarboolShow scrollbar
list_scrollbar_configScrollbarConfigFull scrollbar configuration (variant, gap, thumb, thumb styles)
empty_textimpl Into<Arc<str>>Empty state text (default: "No matches")
empty_text_styleStyleEmpty state text style

list_item_horizontal_padding and list_header_horizontal_padding accept Padding, but only left and right are applied by List.

Item rendering

PropTypeDescription
item_styleStyleItem label base style
description_styleStyleItem description style
description_placementDescriptionPlacementDescription placement: Inline, Right, Above, Below
description_selectionboolWhether selection highlight applies to description text
description_overflowDescriptionOverflowDescription overflow policy: Truncate or Wrap (Wrap applies to Above/Below)
match_styleStyleMatched character style
show_scoresboolShow numeric match scores
score_gradientColorGradientGradient for score coloring
score_range(u64, u64)Explicit score range for gradient
render_itemSearchRenderer<T>Custom item renderer (Arc<dyn Fn>)

description_selection(false) has no effect with DescriptionPlacement::Inline and DescriptionPlacement::Right, because selection/hover styling applies to the whole primary row in those modes.

description_selection also controls description hover styling when list_item_hover_style is set.

description_overflow(DescriptionOverflow::Wrap) affects only DescriptionPlacement::Above and DescriptionPlacement::Below; inline and right placement keep single-row truncation behavior.

Matching config

PropTypeDescription
case_matchingCaseMatchingCase sensitivity (default: Smart)
normalizationNormalizationUnicode normalization (default: Smart)

Matching uses synchronous updates for lists up to sync_match_limit items and off-thread nucleo searches for larger lists; items do not need Send/Sync.

Standalone ranking - rank_search_palette_indices(&[SearchItem<T>], query) returns each item’s index in the source slice ordered like the palette’s fuzzy results (smart case matching and normalization). Use it when another widget owns the query/focus but you need the same ordering for keyboard selection.

As a modal overlay - wrap in Modal and set on_close/size there:

rust
if ctx.state.show_palette {
    let palette = SearchPalette::<Arc<str>>::new()
        .entries(vec![
            SearchEntry::header("Sources"),
            SearchEntry::item("src/lib.rs", Arc::from("src/lib.rs"))
                .description("Crate root"),
            SearchEntry::header("Examples"),
            SearchEntry::item("examples/demo.rs", Arc::from("examples/demo.rs")),
        ])
        .list_scrollbar(true)
        .list_selection_full_width(true)
        .list_item_hover_style(Style::new().bg(Color::DarkGray))
        .on_activate(ctx.link().callback(Msg::Activated));

    Modal::new("Open File")
        .child(palette)
        .width(Length::Px(60))
        .height(Length::Px(20))
        .border_style(BorderStyle::Rounded)
        .padding(0)
        .on_close(ctx.link().callback(|_| Msg::ClosePalette))
}

Inline - embed directly without Modal:

rust
Frame::new()
    .title("Search")
    .border(true)
    .child(SearchPalette::<Arc<str>>::new().items(my_items))

ContextMenu

A popup menu backed by Popover + List. Typically triggered by right-click or a keyboard shortcut.

PropTypeDefaultDescription
triggerimpl IntoElementConstructorThe element that owns the menu
itemsimpl IntoIterator<Item = impl Into<ListItem>>[]Menu items
openboolfalseControlled open state
on_selectCallback<usize>-Fires with selected item index
on_closeCallback<()>-Fires when menu should close
anchorOption<(u16, u16)>NoneAbsolute anchor position (content coordinates)
placementPopoverPlacementBelowStartMenu placement relative to trigger
offsetimpl Into<PopoverOffset>0Distance from trigger
clampbooltrueKeep within screen bounds
auto_flipbooltrueFlip placement when out of bounds
widthLengthPx(20)Menu width
heightLengthAutoMenu height
borderbooltrueShow border
border_styleBorderStylePlainBorder style
paddingimpl Into<Padding>defaultInner padding
styleStyledefaultBase style
selection_styleStyledefaultSelected item style
item_hover_styleStyle-Hovered item style
selection_symbolOption<impl Into<Arc<str>>>"> "Selection indicator
selection_symbol_styleStyle-Selection indicator style
scrollbarboolfalseShow scrollbar
scrollbar_configScrollbarConfigdefaultScrollbar configuration
rust
ContextMenu::new(
    Button::new("Options").on_click(ctx.link().callback(|_| Msg::ToggleMenu))
)
    .items(vec!["Cut", "Copy", "Paste", "Delete"])
    .open(ctx.state.menu_open)
    .on_select(ctx.link().callback(Msg::MenuAction))
    .on_close(ctx.link().callback(|_| Msg::CloseMenu))
    .selection_style(Style::new().bg(Color::DarkGray))

MIT OR Apache-2.0