Router
@lit-material/routerRouter
A tiny router that runs in the browser, for building whole applications with lit-material,
not just individual components. Path matching in the /users/:id style (no regex or
URLPattern dependency), a route outlet that turns <a> clicks on the same
origin into page changes without a reload, and a reactive controller for reading the current route
without owning an outlet.
Routing runs in the browser. It depends on window.history/location.
On the server there is no location, so the outlet renders empty there (the page around it still
renders) and the browser fills it in after hydration.
Install
npm install @lit-material/router
Usage
import "@lit-material/router";
import type { RouteConfig } from "@lit-material/router";
import { html } from "lit";
const routes: RouteConfig<unknown>[] = [
{ path: "/", render: () => html`<home-page></home-page>` },
{ path: "/users/:id", render: (params) => html`<user-page .userId=${params.id}></user-page>` },
{ path: "/*", render: (params) => html`<not-found-page path=${params.wildcard}></not-found-page>` },
];
<lit-material-router-outlet .routes=${routes}></lit-material-router-outlet>
Any <a href="/users/42"> on the same origin, anywhere on the page (including inside
another component's shadow root), changes the page without a reload by itself; plain links need no
code. Use navigate() to navigate from code (redirects, routing after a form submit):
import { navigate } from "@lit-material/router";
navigate("/users/42");
navigate("/login", { replace: true }); // replaces the current history entry instead of pushing
Nested routes
A RouteConfig can declare children: nested routes matched against
whatever's left of the path once the parent's own path is consumed. The parent's
render gets a second argument, outlet, a function that returns the matched
child's content (or null if none matched); call it wherever the nested content should
appear in the parent's own template:
const routes: RouteConfig<unknown>[] = [
{
path: "/dashboard",
render: (params, outlet) => html`
<dashboard-shell>
<nav slot="nav">...</nav>
${outlet()}
</dashboard-shell>
`,
children: [
{ path: "", render: () => html`<dashboard-overview></dashboard-overview>` }, // index route
{ path: "settings", render: () => html`<dashboard-settings></dashboard-settings>` },
{ path: "users/:id", render: (params) => html`<dashboard-user .userId=${params.id}></dashboard-user>` },
],
},
];
/dashboard/settingsrenders the shell with the settings child in place ofoutlet().-
/dashboardhit exactly matches thepath: ""index child first, if one is declared. With no index child, the parent still renders, withoutlet()returningnull. -
/dashboard/nope(nothing underchildrenmatches) means the whole branch doesn't match at all, and matching moves on to the next sibling route. A parent never renders with path segments left over. - Params merge down the tree: a parent's
:paramis still in every descendant'sparams. renderfunctions that take zero or one argument keep working unchanged.
Reading the route without an outlet
For a component that needs to react to navigation (e.g. highlighting the active item in a
NavigationRail) but doesn't render the routed content itself, use
RouteController directly:
import { RouteController } from "@lit-material/router";
import { LitElement, html } from "lit";
class SideNav extends LitElement {
private readonly route = new RouteController(this, () => routes);
override render() {
return html`<a href="/users/42" ?active=${this.route.current.path === "/users/42"}>Profile</a>`;
}
}
API
| Export | Description |
|---|---|
<lit-material-router-outlet> |
Custom element. .routes: RouteConfig[] property; renders the matched route's content, handles link clicks on the same origin. |
RouteConfig<T> |
{ path, render(params, outlet), children? }: a route, optionally with nested routes. |
matchRoute(pattern, pathname) |
Path matcher with no dependencies. Returns { params } or null if pattern doesn't match the whole pathname. |
matchRoutePrefix(pattern, pathname) |
Like matchRoute, but only has to match a prefix. Returns { params, remaining }. |
matchRouteTree(routes, pathname, parentParams?) |
Recursively matches a nested route tree, composing each level's render into the next. |
navigate(path, options?) |
Pushes (or with { replace: true }, replaces) a history entry and notifies mounted outlets/controllers. |
RouteController |
Reactive controller for reading .current route/params without an outlet. |