Skip to content

Router

wybthon.router

router

Client-side router components and navigation helpers for Pyodide apps.

Import it from wybthon.router:

  • Route: declarative mapping of a path pattern to a component, optionally with nested children.
  • Router: component that renders the matched route's component and provides the route context.
  • Link: anchor element that navigates via the History API and toggles an active class.
  • navigate: programmatic navigation helper.
  • current_path: accessor for the current pathname plus query string.
  • use_params / use_query: accessors for the matched route's params and the parsed query string.

The matched component receives params and query as reactive props (declare them by subclassing RouteProps), so navigating between /users/1 and /users/2 updates the mounted component's props instead of remounting it. Path matching (resolve) has no browser dependency, so it also runs in tests, tools, and on the server.

Example
from wybthon.router import Link, Route, RouteProps, Router


@component
def User(props: RouteProps):
    return h1("User ", lambda: props.params()["id"])


Router(
    [Route("/", Home), Route("/users/:id", User)],
    not_found=NotFound,
)

Link("Home", href="/")
See Also

Classes:

Name Description
RouteSpec

Minimal route spec for resolving paths without components (tests and tools).

QueryParams

Decoded query parameters with last-value lookup and get_all.

RouteProps

Props for a routed component: the matched path params and the query string.

Route

Declarative route definition mapping a path to a component.

Functions:

Name Description
resolve

Resolve a pathname to the best matching route and params.

navigate

Navigate within this origin, resolving relative URLs and preserving hashes.

use_params

Accessor for the matched route's params ({} outside a router).

use_query

Accessor for the parsed query string ({} outside a router).

use_base_path

The base path of the surrounding Router ("" outside one).

Router

Render the component of the route matching current_path.

Outlet

Render the next matched child route inside a persistent parent layout.

use_hash

Read the decoded current URL fragment, without its leading hash.

preload

Warm route code and data through the surrounding router.

Link

An anchor that navigates with the History API and marks itself active.

Attributes:

Name Type Description
current_path Accessor[str]

Accessor for the current pathname plus query string.

current_path module-attribute

current_path: Accessor[str] = _CurrentPath()

Accessor for the current pathname plus query string.

Updated by navigate and by the global popstate listener (back/forward navigation). Read it inside reactive scopes to re-render when the URL changes. During a server render it returns the URL passed to the render function.

RouteContext module-attribute

RouteContext: Context[_RouteState | None] = create_context(None, name='Route')

Context the router provides to the matched subtree (params, query, base path).

RouteSpec dataclass

RouteSpec(path: str, children: list[RouteSpec] | None = None)

Minimal route spec for resolving paths without components (tests and tools).

Attributes:

Name Type Description
path str

Route pattern (e.g., "/users/:id").

children list[RouteSpec] | None

Optional nested routes whose paths are joined with this route's path.

QueryParams

QueryParams(search: str = '')

Bases: dict[str, str]

Decoded query parameters with last-value lookup and get_all.

Methods:

Name Description
get_all

Return every occurrence in URL order.

get_all

get_all(key: str) -> list[str]

Return every occurrence in URL order.

RouteProps

RouteProps(**values: Any)

Bases: Props

Props for a routed component: the matched path params and the query string.

Subclass it to add your own fields. Both update in place when the URL changes but the same route still matches.

Route dataclass

Route(path: str, component: Any, children: list[Route] = list(), preload: Callable[[dict[str, Any]], Any] | None = None)

Declarative route definition mapping a path to a component.

Attributes:

Name Type Description
path str

Route pattern (e.g., "/users/:id", "/docs/*").

component Any

The component to render when the path matches. It receives params and query props, accessors yielding the path parameters and query string as dicts.

children list[Route]

Optional nested routes whose paths are joined with this route's path.

resolve

resolve(routes: list[Any], pathname: str, base_path: str = '') -> tuple[Any, dict[str, Any]] | None

Resolve a pathname to the best matching route and params.

The router prefers the most specific segment match and honors a base_path prefix when provided.

Parameters:

Name Type Description Default
routes list[Any]

Flat or nested route specs (any object exposing path and optional children).

required
pathname str

The current URL pathname.

required
base_path str

Optional base path stripped from pathname before matching. When pathname doesn't start with base_path, the function returns None.

''

Returns:

Type Description
tuple[Any, dict[str, Any]] | None

A tuple (route, payload) where payload contains a

tuple[Any, dict[str, Any]] | None

"params" dict, or None when no route matches.

navigate

navigate(path: str, *, replace: bool = False, scroll: bool = True) -> None

Navigate within this origin, resolving relative URLs and preserving hashes.

External URLs use normal browser navigation. Back/forward restores recorded scroll positions; ordinary navigation scrolls to a hash target or the top once the destination's transition commits. A no-op during a server render.

use_params

use_params() -> Accessor[dict[str, Any]]

Accessor for the matched route's params ({} outside a router).

use_query

use_query() -> Accessor[QueryParams]

Accessor for the parsed query string ({} outside a router).

use_base_path

use_base_path() -> str

The base path of the surrounding Router ("" outside one).

Router

Router(routes: Any, *, base_path: Any = '', not_found: Any = None) -> VNode

Render the component of the route matching current_path.

Only a change in which route matches re-mounts the outlet; param and query changes flow into the mounted component as prop updates.

Parameters:

Name Type Description Default
routes Any

A list of Routes (or an accessor returning one).

required
base_path Any

Base path stripped before matching, and prepended by Links underneath.

''
not_found Any

Optional component rendered when no route matches. Falls back to a literal "Not Found" <div>.

None

Returns:

Type Description
VNode

A component VNode.

Outlet

Outlet() -> VNode

Render the next matched child route inside a persistent parent layout.

use_hash

use_hash() -> Accessor[str]

Read the decoded current URL fragment, without its leading hash.

preload

preload(path: str) -> Any

Warm route code and data through the surrounding router.

Link(*children: Any, href: Any = '/', replace: bool = False, active_class: str | None = 'active', end: bool = False, **rest: Any) -> VNode

An anchor that navigates with the History API and marks itself active.

Modifier-key clicks (Cmd/Ctrl/Shift) and middle-clicks are passed through to the browser so users can open links in a new tab.

Parameters:

Name Type Description Default
*children Any

Link content.

()
href Any

Target path (or an accessor). Joined with the surrounding router's base path unless it starts with http://, https://, or #.

'/'
replace bool

Replace the current history entry instead of pushing.

False
active_class str | None

Class added while the link matches the current path. None disables the active class.

'active'
end bool

When True, the link is active only on an exact match; otherwise it's also active for nested paths.

False
**rest Any

Forwarded to the <a> element (class_, on_click, aria_label, ...).

{}
Example
Link("Users", href="/users", class_="nav-link", end=True)

What's in this module

A client-side router built on the History API, plus the pure path matcher behind it. Import it from wybthon.router; these names aren't re-exported from wybthon. Only a change in which route matches remounts the outlet; param and query changes flow into the mounted component as prop updates, so navigating from /users/1 to /users/2 keeps the same component instance.

Name Description
Route Route(path, component, children=[], preload=None); patterns support :param and a * wildcard. preload(params) warms data before the route shows.
RouteProps Props for a routed component: params: Prop[dict[str, str]] and query: Prop[QueryParams]. Subclass it to add fields.
Router Router(routes, *, base_path="", not_found=None); renders the matched component with params and query props.
Outlet Renders the next matched child route inside a parent route's layout.
Link Link(*children, href="/", replace=False, active_class="active", end=False, **rest); an <a> that navigates and marks itself active.
navigate navigate(path, *, replace=False, scroll=True); push or replace a history entry and update current_path.
current_path Accessor for the pathname plus query string; updated by navigate and popstate.
use_params Accessor for the matched route's params dict ({} outside a router).
use_query Accessor for the parsed QueryParams (empty outside a router).
use_hash Accessor for the decoded URL fragment, without its #.
use_base_path The surrounding router's base path ("" outside one).
preload preload(path): warm a route's lazy component and data through the surrounding router.
QueryParams A dict of the last value per key, plus get_all(key) for repeated keys.
resolve, RouteSpec The browser-independent matcher, usable in tests, tools, and on a server.
from wybthon import component, div, h1, http_status, nav
from wybthon.router import Link, Outlet, Route, RouteProps, Router


@component
def Home():
    return h1("Home")


@component
def Users():
    return div(h1("Users"), Outlet())  # child routes render here


@component
def User(props: RouteProps):
    return h1(
        "User ",
        lambda: props.params()["id"],
        lambda: f" (tab={props.query().get('tab', 'info')})",
    )


@component
def NotFound(props: RouteProps):
    http_status(404)  # sets the status during a server render
    return h1("Not found")


@component
def App():
    return div(
        nav(Link("Home", href="/", end=True), Link("Ada", href="/users/1?tab=posts")),
        Router(
            [Route("/", Home), Route("/users", Users, children=[Route(":id", User)]), Route("/docs/*", Home)],
            base_path="/app",
            not_found=NotFound,
        ),
    )
  • A trailing /* captures the rest into params["wildcard"] and also matches the parent path (/docs/* matches /docs). Nested children routes join their paths with the parent's; the parent renders the child through Outlet().
  • Link joins href with the router's base_path unless it starts with http://, https://, or #. Modifier-key and middle clicks fall through to the browser. end=True makes the active class exact-match only. A Link's own on_click receives the event.
  • Outside a browser, navigate only updates the current_path signal, and during a server render it's a no-op; call flush afterward in tests.

Path matching

resolve(routes, pathname, base_path="") returns (route, {"params": {...}, "matches": [...]}) or None, where matches lists the matched route and its ancestors. Any object with path and children attributes works as a route, including Route and the minimal RouteSpec.

Pattern Matches Params
/users /users {}
/users/:id /users/42 {"id": "42"}
/docs/* /docs, /docs/intro, /docs/a/b {"wildcard": ""} or {"wildcard": "intro"}
/files/*/raw /files/a/b/raw {"wildcard": "a/b"}

Nested children join their paths with the parent's (a child path starting with / is absolute). When several routes match, the most specific pattern wins. base_path is stripped before matching; a pathname outside the base returns None.

from wybthon.router import RouteSpec, resolve

routes = [
    RouteSpec("/"),
    RouteSpec("/users", children=[RouteSpec(":id")]),
    RouteSpec("/docs/*"),
]

resolve(routes, "/users/42")  # (RouteSpec(path=':id', ...), {"params": {"id": "42"}, "matches": [...]})
resolve(routes, "/docs/intro/setup")  # (..., {"params": {"wildcard": "intro/setup"}, ...})
resolve(routes, "/app/users/7", base_path="/app")
resolve(routes, "/missing")  # None

Param values are the raw matched segments; the browser router decodes query strings separately.

See also