Skip to main content

Router

Everything here comes from @kog/router. It is a strict subset of React Router v7's declarative API — see the Routing guide for the model, and the notes below for exact signatures.

Components

ComponentPropsNotes
<Router> / <MemoryRouter>initialEntries?: To[], initialIndex?: numberThe router root. Router is an alias; history is in memory (no URL bar). Defaults to ["/"].
<Routes>location?, screen?: booleanMatches the current location (or the scoped override) against its <Route> children. Descendant location hooks see the override. screen defaults to true: the depth-0 match owns an LVGL screen. Set false to render every level in place.
<Route>path?, index?, caseSensitive?, element?, Component?, children?Config only — renders nothing itself. element={<Home />} is the canonical form; Component={Home} also works.
<Outlet>context?: unknownRenders the next matched level into this position. context is read by useOutletContext().
<Navigate>to, replace?, state?, relative?Redirects on mount. A cycle of 10 hops throws with the offending chain.
<Link>to, replace?, state?, relative?, disabled?, style?A Pressable that navigates on press.
<NavLink><Link> props plus end?, caseSensitive?style may be a function receiving { isActive }; it re-runs on every navigation. Matching is prefix-based by default; end requires an exact match. A link to / is exact automatically.

Hooks

HookReturnsNotes
useNavigate()NavigateFunctionnavigate(to, opts?) or navigate(delta). Safe to call from an event handler.
useLocation<S>()Location<S>{ pathname, search, hash, state, key }. Live inline; body derivations (const { pathname } = useLocation()) are kept live by the compiler.
useParams<P>()Readonly<P>Matched params, ancestors merged in. Values are strings when present; an absent optional param is undefined. Safe as a snapshot (see below).
useSearchParams(init?)[SearchParams, SetSearchParams]A live view of the query string, so search.get('q') is reactive as written.
useMatch(pattern)PathMatch | nullThe "is this tab active?" primitive. Live inline or as a compiler-lowered body derivation.
useResolvedPath(to, opts?)PathResolves to exactly as navigate would.
useOutletContext<T>()TThe value the parent route passed via <Outlet context>.
useNavigationType()'PUSH' | 'REPLACE' | 'POP'How the current location was reached.
useInRouterContext()booleanFor library code that must degrade outside a router.
navigate(to: To, options?: { replace?: boolean; state?: unknown; relative?: 'route' | 'path' }): void
navigate(delta: number): void

relative defaults to 'route': a relative to resolves against the current route's matched base, so navigate('edit') from /u/7 under <Route path="u/:id"> lands on /u/7/edit. A target with no pathname (such as '?sort=asc') keeps the current location's pathname. The difference between 'route' and 'path' matters for ..: route mode climbs one route, even when its pattern consumed several URL segments; path mode removes one URL segment from the same route base.

Path patterns

PatternMatches
/usersexactly /users
/users/:id/users/7{ id: '7' }
/users/:id?/users and /users/7
/:lang?/about/about and /en/about
/files/*/files/a/b.txt{ '*': 'a/b.txt' }
indexthe parent route's own path

Branches are ranked by specificity, not declaration order: a static segment beats a dynamic one, which beats a splat. So /u/new wins over /u/:id however you order them. Ties break by declaration order.

Params are decoded with decodeURIComponent, falling back to the raw segment if it is malformed. Matching is case-insensitive unless caseSensitive is set.

Pure helpers

matchPath, matchRoutes, resolvePath, createPath, parsePath and generatePath are exported as plain functions. They need no host, so they are usable (and testable) outside a component.

Reactivity

Location-derived data stays live along two compiler paths: any prop expression containing a call is bound as a dynamic thunk, and a component-body binding derived from a router hook (useLocation, useMatch, useResolvedPath, useNavigationType, useOutletContext, useSearchParams) is auto-lowered to a live memo:

const { pathname } = useLocation();
<Text>{pathname}</Text> // live — lowered to a memo read
<Text>{useLocation().pathname}</Text> // live — dynamic thunk

useParams() needs no lowering — params are part of a route level's rebuild key, so the subtree remounts when they change and a snapshot is always current. An impure derivation from a router hook is a loud compile error, never a silent freeze.

Board size budget

The router is the largest single thing you can add to an app, so it interacts with your board's app-size ceiling. Measured 2026-08-02, compiled bytecode:

AppBytecode
A typical non-router example30–38 KB
Two routes, useNavigate + useParams~56 KB
examples/router-demo (nested routes, NavLink, params)~69 KB

The board advertises what it accepts and the CLI/Studio refuse an over-cap app up front with the real number — you get an actionable error, never a blank screen:

BoardCeiling (hot reload / install)
Waveshare ESP32-S3 1.46B (PSRAM)256 KB — every router app fits
ESP32-2432S028R "CYD" (no PSRAM)76 KB — the full router-demo fits (~69 KB on the wire)

The CYD hot-reload ceiling is the 96 KB JS arena minus the relocator's scratch reserve. The incoming bytecode itself streams to an alternating flash scratch region, so no bundle-sized internal-RAM buffer competes with the app. Production still gains the dev-link's small RX/task allocations back.

Not included

createMemoryRouter / <RouterProvider>, loaders and actions, useRoutes (it must return an element, but a Kog component builds into a parent handle), BrowserRouter / HashRouter, useHref, Form, and ScrollRestoration. Route tables must be static. Screen transition animations are not wired yet.