---
title: "JavaScript Router"
description: "The LemonadeJS Router block for JavaScript: Client-side route outlet. Contract-verified, framework-agnostic, zero dependencies, with a live example."
source: https://lemonadejs.com/docs/plugins/router/
---

<link rel="stylesheet" href="/v6/router.css">

# JavaScript Router

`@lemonadejs/router` · ✓ 11 contract checks · framework-agnostic · zero dependencies

`<Router />` — SPA router ported from v5 with full behavioral parity:

  - routes as data: { path, component | url, preload, title,
    onenter, onleave } — path is exact, a regex string (v5) or a
    ":param" pattern (new: params arrive as props on the component)
  - pages are created lazily and CACHED — revisits reshow the same
    DOM; "single" keeps only the active page attached (v5)
  - remote views: url fetched with the v5 headers + cache buster,
    in-flight requests aborted on navigation, lm-router-loading
    progress bar while fetching
  - global link interception (internal `<a>` click = SPA navigation),
    history.pushState + popstate — both REMOVED on unmount (v5
    leaked these listeners forever; v6 routers destroy clean)
  - slide animation between sibling pages by route order (v5)
  - document.title from route.title or the page's first `<h1>` (v5)

v5 → v6 mapping: controller → component (by value, the v6 way);
declaring routes as HTML children was dropped — routes are a typed
prop. onbeforechangepage(path, route) may cancel (false), redirect
(string) or replace (Route). onchangepage(route, previous, isNew).

## Example

<!--example-->

```js
import { html } from 'lemonadejs';
import Router from '@lemonadejs/router';

const base = window.location.pathname;   // the page this example lives on
const Overview = () => html`<h3>Overview</h3><p>Pages are created once and cached — revisit this tab and the DOM is reused.</p>`;
const Team = () => html`<h3>Team</h3><p>Ana Souza, Marcus Lee and Priya Patel.</p>`;
const Member = (props) => html`<h3>Member #${props.id}</h3><p>A <code>:param</code> route — the id arrives as a prop.</p>`;

const App = (props, { state }) => {
    const visited = state(0);
    const routes = [
        { path: base, component: Overview },
        { path: base + '/team', component: Team },
        { path: base + '/team/:id', component: Member },
    ];
    // Links that are not ours (the docs sidebar) keep their normal page load
    const guard = (path) => routes.some((r) => path.startsWith(r.path)) || (void (window.location.href = path), false);

    return html`<div style="border: 1px solid #e4e4e7; border-radius: 8px; overflow: hidden;">
        <nav style="display: flex; gap: 16px; padding: 10px 16px; border-bottom: 1px solid #e4e4e7;">
            <a href="${base}">Overview</a>
            <a href="${base + '/team'}">Team</a>
            <a href="${base + '/team/7'}">Member 7</a>
            <span style="margin-left: auto;">visits: <b>${visited}</b></span>
        </nav>
        <div style="padding: 0 16px; min-height: 100px;">
            <${Router} routes="${routes}" animation onbeforechangepage="${guard}"
                onchangepage="${() => visited.value++}" />
        </div>
    </div>`;
};
```

## Installation

```bash
npm install @lemonadejs/router
```

```js
import Router from '@lemonadejs/router';
import '@lemonadejs/router/style.css';
```

Three deployment forms, one component:

```js
html`<${Router} />`                       // by value (no registration)
setComponents({ Router });               // then <Router /> by name anywhere
createWebComponent(Router);              // <lm-router> in plain HTML/any framework
```

## Props

Every declared prop arrives as a **live state** — pass a value for a snapshot or a
state for a two-way live wire. Attribute strings are coerced to the declared type.

| Prop | Type | Default | Description |
|---|---|---|---|
| `routes` | array | — |  |
| `single` | boolean | `false` | v5: one page attached at a time |
| `animation` | boolean | `false` | v5: slide between pages by order |

## Events

All event names are lowercase (the platform convention — LJS-305 warns otherwise).

- `onchangepage` — (route, previous, isNew)
- `onbeforechangepage` — (path, route) -> false | path | Route
- `onbeforecreatepage` — (route, html) -> false cancels

## API

```js
import { ref } from 'lemonadejs';
const router = ref();
html`<${Router} ref="${router}" />`;
// router.current.setPath(...)  ·  router.current.current(...)
```

- `setPath()`
- `current()`

## Styling

All classes follow the `lm-router-*` convention; visual variants are `data-*`
attributes on the root. Override freely — there is no styling engine to fight.

## Contract

The machine-readable schema ships with the package:

```js
import contract from '@lemonadejs/router/contract.json';
```

`verify.json` carries the conformance proof produced by `verify(Router)`.

Looking for the v5 plugin? See the [archived v5 documentation](/docs/v5/plugins/).