Shells

Layout wrappers that surround route content. Shells are decoupled from URL structure โ€” a flat route like /settings can share a shell with /dashboard without nesting.

Defining a Shell

Shell modules live in src/shells/ and export a Shell component:

src/shells/app.tsx
import type { ShellProps } from "@pracht/core";

export function Shell({ children }: ShellProps) {
  return (
    <div class="app-layout">
      <nav class="sidebar">
        <a href="/dashboard">Dashboard</a>
        <a href="/settings">Settings</a>
      </nav>
      <main>{children}</main>
    </div>
  );
}

Shell Head Metadata

Shells can contribute to <head> by exporting a head function. Shell metadata merges with route-level metadata:

// Shell exports head() for shared metadata
export function head() {
  return {
    title: "My App",
    meta: [{ name: "viewport", content: "width=device-width, initial-scale=1" }],
  };
}

// Route can override title, arrays are concatenated
export function head() {
  return { title: "Dashboard โ€” My App" };
}
[!NOTE] Route `title` overrides shell `title`. Array fields like `meta` and `link` are merged.

Assigning Shells

Register shells by name in defineApp, then reference them in routes or groups:

src/routes.ts
export const app = defineApp({
  shells: {
    public: "./shells/public.tsx",
    app: "./shells/app.tsx",
  },
  routes: [
    // Per-route
    route("/", "./routes/home.tsx", { shell: "public" }),

    // Per-group โ€” all children inherit
    group({ shell: "app" }, [
      route("/dashboard", "./routes/dashboard.tsx"),
      route("/settings", "./routes/settings.tsx"),
    ]),
  ],
});

Client-Side Navigation

When navigating between routes that share the same shell, pracht preserves the shell and only re-renders the route content. When crossing shell boundaries, the full page tree is re-rendered.