NarraLeaf

Page Router (LayoutRouter)

An overview of the LayoutRouter page routing API, its nested Layout and Page components, path patterns, and migration from PageRouter.

LayoutRouter works together with Layout and Page components to build a tree-like UI structure. It is the recommended page routing API for menus, settings screens, galleries, and layered game UI.

This document gives a quick overview; detailed APIs are in:

Quick example

import { RouterProvider, RootLayout, Layout, Page, useRouter } from "narraleaf-react";

function UI() {
  return (
    <RouterProvider>
      <RootLayout>
        {/* /home */}
        <Layout name="home">
          <Page name={null}> // default page /home
            <Home />
          </Page>
          <Page name="detail"> // /home/detail
            <Detail />
          </Page>
        </Layout>

        {/* /about */}
        <Layout name="about">
          <Page name={null}>
            <About />
          </Page>
        </Layout>
      </RootLayout>
    </RouterProvider>
  );
}

function MenuButton() {
  const router = useRouter();
  return (
    <button onClick={() => router.navigate("/about")}>Go to about</button>
  );
}

Path patterns (slugs & wildcards)

Layout and Page names support two special tokens:

  1. Slug / parameter – :param matches a single segment and exposes its value.
  2. Wildcard – * matches one segment (can be repeated).
// /user/:id/profile
<Layout name="user">
  <Layout name=":id"> {/* matches any user id */}
    <Page name="profile"> {/* e.g. /user/42/profile */}
      <UserProfile />
    </Page>
  </Layout>
</Layout>

Extract parameter values via the router helper:

const router = useRouter();
const params = router.extractParams(router.getCurrentPath(), "/user/:id/profile");
console.log(params.id); // "42"

Wildcard example – match everything under /docs/**:

<Layout name="docs">
  <Page name="*"> {/* /docs/installation, /docs/api/x, ... */}
    <DocViewer />
  </Page>
</Layout>

Why the change?

  1. Nested layouts – Build complex UIs (side-bars, popups, tabs) by composing layouts.
  2. URL-like paths – Use familiar path patterns (/user/:id/profile).
  3. Better animation control – Exit/enter animations propagate through the layout tree.

Migration Guide

Old APINew APINotes
<Page id="about" /><Page name="about" />id ➜ name
router.push("about")router.navigate("/about")paths start with /
No concept<Layout name="home">...</Layout>new container

Next steps

  1. Read the LayoutRouter public methods.
  2. Learn about Layout and path patterns.
  3. Update your project: replace Page id with Page name, wrap related pages in Layouts, and use router.navigate.

Component animations

LayoutRouter mounts and unmounts Page / Layout components automatically. To add enter/exit transitions, simply make the top-level node of your component a Framer Motion element. No extra router code needed.

import { motion } from "framer-motion";

export function PageContent() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 20 }}
      transition={{ duration: 0.3 }}
    >
      {/* your page UI */}
    </motion.div>
  );
}

Wrap PageContent inside a <Page> and the transition will run whenever the route changes.

On this page