GPUI Box GitHub

Components / navigation

NavStack builder

Active navigation content. Inactive pages are not mounted, so their input handlers and accessibility nodes cannot remain active during a transition. Uses a token-backed fade on each accepted history change; reduced motion settles immediately. It restores the last focused descendant on revisits when focus was inside the outgoing page, and never steals external focus.

A builder is RenderOnce. Construct and mount it in one expression.

use gpui_kit::navigation::NavStack;

Reviewed in nav-stack nav-back-preview.

Construct
new(ident: impl Into<Ident>, history: &NavHistory, label: impl Into<SharedString>, focus: FocusHandle, content: impl IntoElement) -> Self
Options

Chain onto the value.

back_transition(transition: &BackTransition) -> Self
Example · nav-stack
fn nav_stack(window: &mut Window, cx: &mut App) -> AnyElement {
    use crate::navigation::nav_stack::{NavHistory, NavStack};
    let theme = cx.theme().clone();
    let state = window.use_keyed_state("scene.nav-stack.state", cx, |_, cx| {
        (
            NavHistory::new("overview"),
            cx.focus_handle(),
            cx.focus_handle(),
            cx.focus_handle(),
        )
    });
    let (history, overview, details, replacement) = state.read(cx).clone();
    let focus = match history.current().as_ref() {
        "details" => details,
        "replacement" => replacement,
        _ => overview,
    };
    let back = state.clone();
    let forward = state.clone();
    let push = state.clone();
    let replace = state.clone();
    stack(&theme).w(px(620.0))
        .child(caption(&theme, "Caller-owned visit history; back retains forward visits, push forks, replace preserves branches"))
        .child(row(&theme)
            .child(Button::new("scene.nav-stack.back").label("Back").disabled(!history.can_pop())
                .on_click(move |_, cx| back.update(cx, |state, cx| { state.0.pop(); cx.notify(); })))
            .child(Button::new("scene.nav-stack.forward").label("Forward").disabled(!history.can_forward())
                .on_click(move |_, cx| forward.update(cx, |state, cx| { state.0.forward(); cx.notify(); })))
            .child(Button::new("scene.nav-stack.push").label("Push details")
                .disabled(history.entries().iter().any(|id| id.as_ref() == "details"))
                .on_click(move |_, cx| push.update(cx, |state, cx| { state.0.push("details"); cx.notify(); })))
            .child(Button::new("scene.nav-stack.replace").label("Replace")
                .disabled(history.entries().iter().any(|id| id.as_ref() == "replacement"))
                .on_click(move |_, cx| replace.update(cx, |state, cx| { state.0.replace("replacement"); cx.notify(); }))))
        .child(NavStack::new("scene.nav-stack", &history, history.current().clone(), focus,
            Card::new().padding(Space::Xl)
                .child(crate::foundation::text(&theme, TypeScale::Title, history.current().clone()))
                .child(crate::foundation::text(&theme, TypeScale::Body,
                    "Only the active page is mounted. Return visits restore focus without keeping inactive controls live."))))
        .into_any_element()
}

Source: crates/gpui-kit/src/navigation/nav_stack.rs