gpui-navigator
Declarative client-side navigation for GPUI (Zed's GPU-accelerated UI framework). Provides route matching, nested layouts, animated transitions, guards, middleware, and LRU caching — all behind feature flags so you pay only for what you use.
Table of Contents
- Features
- Installation
- Quick Start
- Defining Routes
- Navigation API
- Widgets
- Nested Routing
- Route Parameters
- Transitions
- Route Guards
- Middleware
- Route Lifecycle
- Error Handling
- Caching
- Feature Flags
- Examples
- Architecture Overview
- API Reference
- License
Features
- Smooth Transitions — Fade, slide (4 directions) with configurable duration and dual enter/exit animation
- Nested Routing — Unlimited nesting depth with
RouterOutlet, named outlets, index routes - Stateful Components —
Route::component()auto-caches GPUI entities across navigations - Route Guards —
AuthGuard,RoleGuard,PermissionGuard, composableNotGuard - Middleware — Before/after navigation hooks with priority ordering
- Named Routes — Navigate by name with parameter substitution
- Route Lifecycle —
on_enter,on_exit,can_deactivatehooks - LRU Cache — Route resolution caching with hit-rate stats
- Error Pages — Built-in styled 404 and error pages, fully customizable
- RouterLink — Navigation links with automatic active-state styling
- Type-safe Params —
get_as::<T>()for parsed parameter extraction - Logging —
logortracingbackend (mutually exclusive, feature-gated)
Installation
[]
= "0.1"
= "0.2"
All features are enabled by default. To pick only what you need:
[]
= { = "0.1", = false, = ["transition", "guard"] }
Quick Start
use *;
use *;
use *;
;
init_router registers routes globally. RouterView at the top level renders whichever route matches the current path.
Defining Routes
Route::view — Stateless Pages
Simplest option. Takes a path and a closure that returns AnyElement:
view
Route::component — Stateful Pages
Wraps a GPUI Entity that persists across navigations. State is preserved when the user navigates away and back:
component
Route::component_with_params — Stateful + Params
Like component, but the factory receives RouteParams. Each unique parameter set gets its own cached entity:
component_with_params
Route::new — Full Control
Receives &mut Window, &mut App, and &RouteParams:
new
Navigation API
Programmatic Navigation
All methods are static on Navigator:
use Navigator;
// Push a new route onto the history stack
push;
// Replace the current route (no new history entry)
replace;
// Go back / forward in history
pop;
forward;
// Query state
let path: String = current_path;
let can_back: bool = can_pop;
let can_fwd: bool = can_go_forward;
Fluent API
Chain multiple navigations:
of
.push
.push
.push;
Named Routes
Define routes with names, navigate by name with parameter substitution:
// Define
new
.name
// Navigate
let mut params = new;
params.set;
params.set;
push_named;
// Navigates to: /users/42/posts/7
// Generate URL without navigating
let url = url_for;
// Some("/users/42/posts/7")
Widgets
RouterView
Top-level widget that renders the matched route at depth 0. Place this once at the root of your app:
Functional alternative:
RouterOutlet
Renders child routes inside a parent layout. Each RouterOutlet increments the nesting depth:
// In a parent route's component
Named outlet for multiple content areas:
named
RouterLink
Navigation link with automatic active-state detection:
Shorthand:
router_link
Nested Routing
Define parent layouts with child routes that render inside RouterOutlet:
init_router;
Navigating to /dashboard/settings renders the dashboard layout with settings inside its RouterOutlet.
Named Outlets
Route children into different content areas:
new
.children
.named_outlet
Render with RouterOutlet::named("sidebar") in the layout.
Index Routes
A child route with path "" (empty) acts as the index — it renders when the parent path is matched exactly:
new
.children
Parameter Inheritance
Child routes automatically inherit parameters from all ancestor routes:
// Route: /orgs/:org_id/teams/:team_id/members/:member_id
// At the deepest child, params contains: org_id, team_id, member_id
If a child defines a parameter with the same name as a parent, the child value takes precedence (with a debug warning).
Route Parameters
Path Parameters
Define with :name syntax. Extract with RouteParams:
new
Construct programmatically:
let params = from_path;
assert_eq!;
Merge parent and child params:
let merged = merge;
Query Parameters
Parse and serialize query strings:
let qp = from_query_string;
let search: = qp.get;
let page: = qp.get_as;
let tags: = qp.get_all; // multi-value support
let qs: String = qp.to_query_string; // "page=2&search=rust&tag=web&tag=api"
Transitions
Requires feature
transition(enabled by default)
Add animations between route changes:
view
.transition
view
.transition
Available transitions:
| Constructor | Description |
|---|---|
Transition::None |
Instant switch, no animation |
Transition::fade(ms) |
Opacity cross-fade |
Transition::slide_left(ms) |
Slide from right to left |
Transition::slide_right(ms) |
Slide from left to right |
Transition::slide_up(ms) |
Slide from bottom to top |
Transition::slide_down(ms) |
Slide from top to bottom |
Override a transition for a single navigation:
push_with_transition;
set_next_transition;
The library uses a dual animation system: the incoming route's transition drives both exit (old page) and enter (new page) animations simultaneously.
Route Guards
Requires feature
guard(enabled by default)
Guards run before navigation and can allow, deny, or redirect:
use *;
// Authentication — redirect to /login if not authenticated
new
.guard
// Role-based — require "admin" role
new
.guard
// Permission-based
new
.guard
// Invert any guard
new
.guard
// Custom guard with a closure
new
.guard
Guards have a priority() (higher runs first). Multiple guards on a route run in priority order; the first non-Continue result wins.
Middleware
Requires feature
middleware(enabled by default)
Hooks that run before and after every navigation on the route:
use *;
// Using the trait
;
view
.middleware
// Using closures
view
.middleware
Route Lifecycle
Lifecycle hooks for fine-grained control over route activation/deactivation:
use *;
;
view
.lifecycle
NavigationAction variants:
| Variant | Effect |
|---|---|
NavigationAction::Continue / ::allow() |
Allow navigation |
NavigationAction::deny(reason) |
Block navigation |
NavigationAction::redirect(path) |
Redirect to a different route |
NavigationAction::redirect_with_reason(path, reason) |
Redirect with explanation |
Error Handling
Built-in styled 404 and error pages work out of the box. Customize them:
// Per-route error handlers
let handlers = new
.on_not_found
.on_error;
// Global default pages
let pages = new
.with_not_found
.with_error
.with_loading;
NavigationResult returned from navigation operations:
| Variant | Meaning |
|---|---|
Success { path } |
Route matched and rendered |
NotFound { path } |
No route matched the path |
Blocked { reason, redirect } |
Guard or lifecycle denied navigation |
Error(NavigationError) |
Internal error |
Caching
Requires feature
cache(enabled by default, depends onlru)
Route resolution results are cached in an LRU cache:
// Access cache stats through the router
let stats: &CacheStats = router.cache_stats;
println!;
println!;
Feature Flags
| Feature | Default | Description | Dependencies |
|---|---|---|---|
guard |
yes | AuthGuard, RoleGuard, PermissionGuard, NotGuard, guard_fn |
— |
middleware |
yes | RouteMiddleware trait, middleware_fn helper |
— |
transition |
yes | Transition::fade, slide_left/right/up/down |
— |
cache |
yes | LRU route resolution cache | lru |
log |
yes | Logging via the log crate |
log |
tracing |
no | Logging via tracing (mutually exclusive with log) |
tracing |
Examples
# Nested routing with parameter inheritance
# All transition types with live preview
# RouterLink, error pages, dynamic params
# Stateful components with Entity caching
# AuthGuard, RoleGuard, PermissionGuard, guard_fn
# RouteMiddleware trait, middleware_fn, execution logging
# RouteLifecycle: on_enter, on_exit, can_deactivate
Architecture Overview
init_router()
|
GlobalRouter (state.rs, context.rs)
/ | \
Routes History Cache (route.rs, history.rs, cache.rs)
| |
MatchStack NavigationResult (resolve.rs, error.rs)
|
RouterView / RouterOutlet (widgets.rs)
|
Nested resolution (nested.rs, params.rs)
Core flow:
init_routerregisters routes in a globalRouterStateNavigator::push("/path")triggers route matching viaresolve_match_stack- Guards and middleware run in priority order
RouterViewrenders the root match;RouterOutletrenders children at each nesting depth- Transitions animate between the old and new content
Key modules:
| Module | Responsibility |
|---|---|
context.rs |
Navigator static API, init_router, GPUI integration |
route.rs |
Route builder, RouteConfig, named route registry |
resolve.rs |
MatchStack resolution — maps a path to a chain of matched routes |
nested.rs |
Child route resolution, path normalization, parameter extraction |
widgets.rs |
RouterView, RouterOutlet, RouterLink, DefaultPages |
params.rs |
RouteParams (path), QueryParams (query string) |
state.rs |
RouterState — centralized navigation state |
history.rs |
Navigation history stack with back/forward |
guards.rs |
RouteGuard trait and built-in implementations |
middleware.rs |
RouteMiddleware trait with priority ordering |
transition.rs |
Transition enum and TransitionConfig |
lifecycle.rs |
RouteLifecycle trait, NavigationAction enum |
cache.rs |
LRU cache for route resolution with CacheStats |
error.rs |
NavigationError, NavigationResult, ErrorHandlers |
logging.rs |
Unified logging macros (log / tracing backends) |
API Reference
Full API documentation is available on docs.rs.
Quick Reference
| Type / Function | Description |
|---|---|
init_router(cx, |router| { ... }) |
Register routes globally |
Navigator::push(cx, path) |
Navigate to a path |
Navigator::replace(cx, path) |
Replace current path |
Navigator::pop(cx) |
Go back |
Navigator::forward(cx) |
Go forward |
Navigator::current_path(cx) |
Get current path |
Navigator::push_named(cx, name, params) |
Navigate by route name |
Navigator::of(cx).push(p).push(p2) |
Fluent chaining |
Route::view(path, closure) |
Stateless route |
Route::component(path, factory) |
Stateful route (Entity cached) |
Route::component_with_params(path, factory) |
Stateful + params |
Route::new(path, handler) |
Full-control route |
.children(vec![...]) |
Add child routes |
.name("n") |
Name the route |
.transition(Transition::fade(ms)) |
Add transition |
.guard(AuthGuard::new(check, redirect)) |
Add guard |
.middleware(impl RouteMiddleware) |
Add middleware |
.lifecycle(impl RouteLifecycle) |
Add lifecycle hooks |
RouterView::new() |
Root route renderer |
RouterOutlet::new() |
Child route renderer |
RouterOutlet::named("n") |
Named outlet |
RouterLink::new(path).child(el).build(cx) |
Nav link |
RouteParams::get("key") |
Get path param |
RouteParams::get_as::<T>("key") |
Typed extraction |
QueryParams::from_query_string(qs) |
Parse query string |
DefaultPages::new().with_not_found(f) |
Custom error pages |
Minimum Supported Rust Version
Rust 1.75 or later.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new features
- Run
cargo test --all-features && cargo clippy --all-targets --all-features && cargo fmt --check - Open a Pull Request