Overview

Router state lives on the MatesUtils scope that renderApp provides. Read and navigate through useUtils() — there are no global pathAtom / navigateTo imports.

Mount at most one Router or animatedRouter per app. Each outlet calls claimRouterOutlet so a second mount throws. Nest screens with conditionals — not nested routers.

navigateTo and URL atom .set / .update are bidirectional: writing an atom navigates, and navigation updates the atoms.

URL atoms at a glance

Piece
Role
Notes
pathAtom pathname Pathname only — never includes ? or #.
qsAtom / hashAtom parsed objects Synced by navigateTo; writes default to replace.
paramsAtom route params From pathAtom + patternsAtom — not props.
navigateTo history + sync Updates history, then syncs path / qs / hash atoms.
claimRouterOutlet one outlet Used by Router / animatedRouter — mutual exclusion per scope.

navigateTo & atom writes

const { navigateTo, pathAtom, qsAtom, hashAtom, paramsAtom } = useUtils();

navigateTo('/about');                    // pushState
navigateTo('/login', true);              // replaceState
navigateTo('/users/1', false, { from: 'list' }); // history.state

// Bidirectional: atom writes also navigate — second arg is replace
pathAtom.set('/about');                  // push (default)
pathAtom.set('/login', true);            // replace
qsAtom.set({ tab: 'bio' });              // replace (default for qs/hash/params)
paramsAtom.set({ id: '99' });            // replace — rebuilds path from matched pattern

// Query + hash are first-class — pathAtom stays pathname-only
navigateTo('/users/42?tab=bio#section=1');
pathAtom();  // "/users/42"
qsAtom();    // { tab: "bio" }
hashAtom();  // { section: 1 }

// Omit ? or # to preserve the current query / hash
navigateTo('/users/99'); // keeps existing search + hash

Nested UI — conditionals, not nested routers

Keep one outlet, and nest screens with paramsAtom / qsAtom / pathAtom.

import { html, nothing, Router, useUtils, x } from 'mates';

// One Router per app. Nested screens: conditionals on params / qs — not another router.

const GalleryPage = () => {
  const { paramsAtom, navigateTo } = useUtils();

  return () => {
    const { photoId } = paramsAtom();
    return html`
      <h1>Gallery</h1>
      <button @click=${() => navigateTo('/gallery/photo/7')}>Open photo 7</button>
      ${photoId ? x(PhotoOverlay, { id: photoId }) : nothing}
    `;
  };
};

const App = () => {
  const appRouter = Router([
    { path: '/', component: HomePage },
    { path: '/gallery', component: GalleryPage },
    { path: '/gallery/photo/:photoId', component: GalleryPage },
  ]);
  return () => html`${appRouter}`;
};

Tiny Router (two routes)

Real Router + navigateTo in the sandbox iframe (own MatesUtils scope).

import { html, Router, useUtils, renderApp } from 'mates';

const Home = () => () => html`<p class="muted">Home</p>`;
const About = () => () => html`<p class="muted">About</p>`;

const App = () => {
const { navigateTo, pathAtom } = useUtils();
const outlet = Router([
{ path: '/', component: Home },
{ path: '/about', component: About },
]);

return () => html`
<div class="m-col m-gap">
<nav class="tabs">
<button @click=${() => navigateTo('/')}>Home</button>
<button @click=${() => navigateTo('/about')}>About</button>
</nav>
<p class="hint">path=${pathAtom()}</p>
${outlet}
</div>
`;
};
renderApp(App, document.getElementById('app'));

Bidirectional atom.set

pathAtom.set and qsAtom.set call navigateTo. Prefer navigateTo when setting path + query together.

import { html, useUtils, renderApp } from 'mates';

const App = () => {
const { pathAtom, qsAtom, navigateTo } = useUtils();

return () => html`
<div class="m-col m-gap">
<p class="hint">path=${pathAtom()} qs=${JSON.stringify(qsAtom())}</p>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button @click=${() => pathAtom.set('/')}>pathAtom.set('/')</button>
<button @click=${() => pathAtom.set('/about')}>pathAtom.set('/about')</button>
<button @click=${() => qsAtom.set({ tab: 'bio' })}>qsAtom.set</button>
<button class="btn-primary" @click=${() => navigateTo('/about?tab=x')}>navigateTo</button>
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));