Enterprise-grade navigation library for Yew — automatic active state detection and a complete component system.
- Overview
- Installation
- Requirements
- Quick Start
- Components
- CSS Integration
- Architecture
- Examples
- API Reference
- Migration Guides
- Coverage
- Contributing
- License
yew-nav-link is a comprehensive navigation library for the Yew web framework. It provides:
| Feature | Description |
|---|---|
| NavLink | Drop-in replacement for Yew Router's <Link> with automatic active class detection |
| Component System | 15+ ready-to-use UI components (tabs, dropdowns, pagination, badges, icons) |
| Hooks | Reactive hooks for route state, active checking, breadcrumbs, and programmatic navigation |
| Utilities | Path manipulation, URL encoding, keyboard navigation, and query string handling |
| Customization | Custom CSS classes, programmatic navigation, and extensible breadcrumb providers |
The core NavLink component eliminates manual active state tracking. It compares the current route against the target on every render and applies the active CSS class automatically — zero configuration required.
[dependencies]
yew-nav-link = "0.9"| Dependency | Version |
|---|---|
| Rust | 1.95+ |
| Edition | 2024 |
| Yew | 0.23+ |
| yew-router | 0.20+ |
use yew::prelude::*;
use yew_nav_link::NavLink;
use yew_router::prelude::*;
#[derive(Clone, PartialEq, Routable)]
enum Route {
#[at("/")]
Home,
#[at("/about")]
About,
}
#[component]
fn Navigation() -> Html {
html! {
<nav>
<NavLink<Route> to={Route::Home}>{ "Home" }</NavLink<Route>>
<NavLink<Route> to={Route::About}>{ "About" }</NavLink<Route>>
</nav>
}
}When the user visits /about, the second link automatically receives class="nav-link active".
use yew::prelude::*;
use yew_nav_link::{nav_link, Match};
use yew_router::prelude::*;
#[component]
fn Menu() -> Html {
html! {
<nav>
{ nav_link(Route::Home, "Home", Match::Exact) }
{ nav_link(Route::Docs, "Docs", Match::Partial) }
</nav>
}
}Keep parent links highlighted on nested routes:
html! {
<nav>
// Active on /docs, /docs/api, /docs/anything
<NavLink<Route> to={Route::Docs} partial=true>{ "Docs" }</NavLink<Route>>
</nav>
}Partial matching is segment-aware: /docs matches /docs/api but not /documentation.
Customize the default nav-link and active classes:
html! {
<nav>
// Custom base class
<NavLink<Route> to={Route::Home} class="menu-item">{ "Home" }</NavLink<Route>>
// Custom active class
<NavLink<Route> to={Route::About} active_class="is-selected">{ "About" }</NavLink<Route>>
// Both custom
<NavLink<Route> to={Route::Contact} class="sidebar-link" active_class="highlighted">{ "Contact" }</NavLink<Route>>
</nav>
}use_navigation::<R>() returns a [Navigation<R>] handle exposing pre-built Callbacks — no manual Callback::from(...) boilerplate.
use yew::prelude::*;
use yew_nav_link::use_navigation;
#[component]
fn MyComponent() -> Html {
let nav = use_navigation::<Route>();
html! {
<>
// Push a new entry onto the history stack.
<button onclick={nav.push_callback(Route::About).reform(|_: MouseEvent| ())}>
{ "Go to About" }
</button>
// Replace the current entry without growing history.
<button onclick={nav.replace_callback(Route::Home).reform(|_: MouseEvent| ())}>
{ "Replace with Home" }
</button>
// Browser back / forward.
<button onclick={nav.go_back.reform(|_: MouseEvent| ())}>{ "Back" }</button>
<button onclick={nav.go_forward.reform(|_: MouseEvent| ())}>{ "Forward" }</button>
</>
}
}Implement [BreadcrumbLabelProvider] to control how each path segment is rendered. The provider operates on paths (e.g. /docs/api), not on Routable enum variants — it works the same for static and parameterised routes.
use std::rc::Rc;
use yew_nav_link::{BreadcrumbLabelProvider, use_breadcrumbs};
struct MyLabels;
impl BreadcrumbLabelProvider for MyLabels {
fn label_for_path(&self, path: &str) -> String {
match path {
"/" => "Home".into(),
"/about" => "About us".into(),
p if p.starts_with("/users/") => format!("User {}", &p[7..]),
other => other.into(),
}
}
}
#[component]
fn Crumbs() -> Html {
// Provide the implementation through context (omitted for brevity);
// then read the trail.
let trail = use_breadcrumbs::<Route>();
html! {
<nav aria-label="Breadcrumb">
{ for trail.into_iter().map(|item| html! {
<span aria-current={if item.is_active { "page" } else { "" }}>
{ item.label }
</span>
}) }
</nav>
}
}| Component | Purpose |
|---|---|
NavLink<R> |
Navigation link with automatic active state |
[NavList] |
Accessible navigation list container (<ul> with ARIA) |
[NavItem] |
Navigation list item (<li>) |
[NavDivider] |
Visual separator between navigation groups |
| Component | Purpose |
|---|---|
[NavBadge] |
Badge/counter for navigation items |
[NavHeader] |
Section header for navigation groups |
[NavText] |
Plain text element within navigation |
[NavIcon] |
Icon with configurable size |
[NavLinkWithIcon] |
Link with integrated icon |
[NavDropdown] |
Dropdown menu with items and dividers |
[NavTabs] |
Tabbed navigation container |
[NavTab] |
Individual tab with active state |
[NavTabPanel] |
Content panel for tabs |
[Pagination] |
Page navigation controls |
[PageItem] |
Individual page indicator |
[PageLink] |
Clickable page link |
| Hook | Returns | Description |
|---|---|---|
use_route_info() |
RouteInfo<R> |
Current route, path, and query parameters |
use_is_active(route) |
bool |
Whether the given route is currently active |
use_is_exact_active(route) |
bool |
Whether the route matches exactly |
use_is_partial_active(route) |
bool |
Whether the route is a prefix of the current path |
use_breadcrumbs() |
Vec<BreadcrumbItem<R>> |
Auto-generated breadcrumb trail from current route |
use_navigation<R>() |
Navigation<R> |
Programmatic navigation (push, replace, go back/forward) |
use_query_params() |
HashMap<String, String> |
URL query string parameters |
| Function | Description |
|---|---|
is_absolute(path) |
Check if a path starts with / |
join_paths(a, b) |
Join two path segments safely |
normalize_path(path) |
Remove duplicate slashes and trailing slashes |
urlencoding_encode(s) |
Percent-encode a string for URLs |
urlencoding_decode(s) |
Decode a percent-encoded string |
handle_arrow_key(config, key) |
Keyboard navigation handler |
handle_home_end(config, key) |
Home/End key handler for navigation |
Works out of the box — nav-link and active are native Bootstrap classes.
<ul class="nav nav-pills">
<li class="nav-item">
<NavLink<Route> to={Route::Home}>{ "Home" }</NavLink<Route>>
<!-- Renders: <a class="nav-link active" href="https://github.com/">Home</a> -->
</li>
</ul>Define your own nav-link and active styles:
.nav-link {
@apply px-4 py-2 text-gray-600 hover:text-gray-900 transition-colors;
}
.nav-link.active {
@apply text-blue-600 font-semibold border-b-2 border-blue-600;
}yew-nav-link
├── active_link # Core NavLink component + Match enum
├── nav # Primitives: NavList, NavItem, NavDivider
├── components # UI: Badge, Dropdown, Icon, Tabs, Pagination
├── hooks # Reactive and programmatic route/navigation helpers
├── utils # Path, URL, keyboard navigation utilities
├── attrs # Type-safe attribute builders
└── errors # NavError, NavResult types
For why the crate is shaped this way — the trade-offs picked over each
alternative — see docs/ARCHITECTURE.md for the
overview and docs/adr/ for individual architecture
decision records.
A live demo is published at https://raprogramm.github.io/yew-nav-link/. It exercises every component, hook, and utility in the public API.
Run the same demo locally:
rustup target add wasm32-unknown-unknown
cargo install trunk
cd example
trunk serveOpen http://127.0.0.1:3000 (port set in example/trunk.toml).
| Prop | Type | Default | Description |
|---|---|---|---|
to |
R: Routable |
required | Target route |
children |
Children |
required | Link content |
partial |
bool |
false |
Enable prefix matching |
class |
&str |
"nav-link" |
Custom CSS class (replaces default) |
active_class |
&str |
"active" |
Custom active state class |
| Variant | Behavior |
|---|---|
Exact |
Active only on exact path match |
Partial |
Active when current path starts with target (segment-wise) |
fn nav_link<R: Routable + PartialEq + Clone + 'static>(
to: R,
children: &str,
match_mode: Match,
) -> Htmlpub struct BreadcrumbItem {
pub label: String,
pub route: Option<String>,
pub is_current: bool,
}| File | Purpose |
|---|---|
| Architectural book | Rendered mdBook of the documents below — search, syntax-highlighting, navigation. Source lives under docs/. |
docs/REQUIREMENTS.md |
Functional and non-functional requirements (what the crate does, the constraints under which it does it) |
docs/ARCHITECTURE.md |
Module layout, the active-state algorithm, hook contracts, breadcrumb context flow |
docs/ROADMAP.md |
Trajectory through 0.10.x (current line) toward 1.0 (API freeze) |
docs/BRANCHING.md |
Branching, commit, and merge policy enforced on main |
SECURITY.md |
Coordinated disclosure policy |
CONTRIBUTING.md |
Workflow, commit format, code standards |
For a full release-by-release log, see CHANGELOG.md.
| From | To | Notes |
|---|---|---|
| 0.8.x | 0.9.x | Macro feature removed; component/function APIs unchanged. See CHANGELOG [0.9.0]. |
| 0.9.0 | 0.9.1 | Single-file SPA demo replaces the multi-page docs site under example/; library API unchanged. |
| 0.9.1 | 0.9.2 | BreadcrumbLabelProvider now re-exported at the crate root. Drop-in upgrade. |
Target: 95%+ coverage, tracked via Codecov.
The inner-most circle is the entire project, moving outward are folders then individual files. Size and color represent statement count and coverage.
Each block represents a single file. Size and color represent statement count and coverage.
Top section is the entire project, proceeding through folders to individual files.
See CONTRIBUTING.md for the contribution workflow and code standards.
Licensed under the MIT License.