---
name: layout-navigation
description: >
  Configure LayoutProvider with theme, options, and Navigation structure.
  Shell: AppBar with env chip + NavigationRail (permanent sm+, modal xs).
  Navigation items: page (title, icon, to/href), header, divider.
  LayoutOptions: showShell, snackbarOrigin, publicRoutes. Dark mode via
  theme.applyStyles("dark", ...) with colorSchemeSelector
  data-mui-color-scheme. i18n with i18next, locale files auto-detected.
  Activate when configuring the layout shell, navigation, or theming.
type: core
library: wcz-layout
library_version: "7.6.1"
sources:
  - "wcz-layout:src/providers/LayoutProvider.tsx"
  - "wcz-layout:src/components/core/Layout.tsx"
  - "wcz-layout:src/models/Navigation.ts"
  - "wcz-layout:src/models/LayoutOptions.ts"
  - "wcz-layout:src/lib/theme.ts"
---

# Layout & Navigation

## Setup

The layout shell is configured in the root route via `LayoutProvider`:

```typescript
// src/routes/__root.tsx
import { LayoutProvider, rootRouteHead, RouterNotFound, RouterError } from "wcz-layout";
import { theme } from "~/lib/theme";
import { navigation } from "~/navigation";
import type { LayoutOptions } from "wcz-layout/models";

const options: LayoutOptions = {
  showShell: true,
  publicRoutes: ["/login"],
};

export const Route = createRootRouteWithContext()({
  head: rootRouteHead,
  component: RootComponent,
  notFoundComponent: RouterNotFound,
  errorComponent: RouterError,
});

function RootComponent() {
  return (
    <LayoutProvider theme={theme} navigation={navigation} options={options}>
      <Outlet />
    </LayoutProvider>
  );
}
```

## Core Patterns

### Navigation structure

Navigation is an array of items with three types — discriminated by the presence of `kind`:

```typescript
import type { Navigation } from "wcz-layout/models";
import HomeIcon from "@mui/icons-material/Home";
import ListIcon from "@mui/icons-material/List";
import SettingsIcon from "@mui/icons-material/Settings";

export const navigation: Navigation = [
  { title: "Home", icon: <HomeIcon />, to: "/" },
  { kind: "divider" },
  { kind: "header", title: "Management" },
  { title: "Todos", icon: <ListIcon />, to: "/todos" },
  { kind: "divider" },
  { title: "Settings", icon: <SettingsIcon />, to: "/settings" },
];
```

### Navigation item types

| Type      | Shape                       | Required fields |
| --------- | --------------------------- | --------------- |
| Page item | `{ title, icon, to }`       | `title`, `icon` |
| Divider   | `{ kind: "divider" }`       | `kind`          |
| Header    | `{ kind: "header", title }` | `kind`, `title` |

Page items also accept:

- `to` — internal route (TanStack Router link, type-safe)
- `href` — external URL (opens in new tab)
- `params`, `search` — TanStack Router link params
- `children` — nested navigation items (sub-menu)
- `hidden` — conditionally hide the item

### Nested navigation

```typescript
const navigation: Navigation = [
  {
    title: "Admin",
    icon: <AdminIcon />,
    to: "/admin",
    children: [
      { title: "Users", icon: <PeopleIcon />, to: "/admin/users" },
      { title: "Roles", icon: <SecurityIcon />, to: "/admin/roles" },
    ],
  },
];
```

### LayoutOptions

```typescript
interface LayoutOptions {
  showShell?: boolean; // Show AppBar + NavigationRail (default: true)
  snackbarOrigin?: SnackbarOrigin; // Notification position
  publicRoutes?: string[]; // Routes that skip authentication
}
```

### Theme configuration

The theme is pre-configured in the template at `src/lib/theme.ts`. Use MUI's `extendTheme` to customize:

```typescript
// src/lib/theme.ts
import { extendTheme } from "@mui/material/styles";

export const theme = extendTheme({
  colorSchemes: {
    light: { palette: { primary: { main: "#1976d2" } } },
    dark: { palette: { primary: { main: "#90caf9" } } },
  },
  colorSchemeSelector: "data-mui-color-scheme",
});
```

### Dark mode styling

Always use `theme.applyStyles("dark", ...)` for mode-specific styling:

```typescript
<Box
  sx={(theme) => ({
    backgroundColor: "#fff",
    ...theme.applyStyles("dark", {
      backgroundColor: "#121212",
    }),
  })}
/>
```

### i18n setup

Locale files in `src/locales/` are auto-detected by `viteWczLayout()`:

```
src/locales/en.json    # English (default)
src/locales/cs.json    # Czech
```

The Vite plugin generates the `virtual:wcz-layout` module with all locale resources. `LayoutProvider` initializes `i18next` with language detection (cookie-based, 1-year expiry) and syncs Zod and DayJS locales on language change.

```typescript
import { useTranslation } from "react-i18next";

function MyComponent() {
  const { t } = useTranslation();
  return <Typography>{t("welcome")}</Typography>;
}
```

### Root route head

`rootRouteHead` provides meta tags and manifest link:

```typescript
export const Route = createRootRouteWithContext()({
  head: rootRouteHead,
  // optionally: head: rootRouteHead({ manifest: "/manifest.json" }),
});
```

### Service worker

`LayoutProvider` registers `/sw.js` on mount for PWA support.

## Common Mistakes

### HIGH Using theme.palette for dark mode instead of theme.applyStyles

Wrong:

```typescript
sx={{ color: mode === "dark" ? "white" : "black" }}
```

Correct:

```typescript
sx={(theme) => ({
  color: "black",
  ...theme.applyStyles("dark", { color: "white" }),
})}
```

The app uses `colorSchemeSelector: "data-mui-color-scheme"`. Mode-specific styles must use `theme.applyStyles("dark", ...)`, not conditional palette checks or mode variables.

Source: copilot-instructions.md / wcz-layout:src/lib/theme.ts

Cross-skill: See also skills/ui-pages/SKILL.md § Common Mistakes

### HIGH Using href for internal routes in Navigation

Wrong:

```typescript
{ title: "Todos", icon: <ListIcon />, href: "/todos" }
```

Correct:

```typescript
{ title: "Todos", icon: <ListIcon />, to: "/todos" }
```

`NavigationPageItem` supports dual destination: `to` for internal TanStack Router links, `href` for external URLs. Using `href` for internal routes causes full page reloads.

Source: wcz-layout:src/models/Navigation.ts

### MEDIUM Creating NavigationPageItem without icon

Wrong:

```typescript
{ title: "Todos", to: "/todos" }
```

Correct:

```typescript
{ title: "Todos", icon: <ListIcon />, to: "/todos" }
```

`NavigationPageItem` requires both `title` and `icon`. Missing `icon` renders an empty slot in the NavigationRail.

Source: wcz-layout:src/models/Navigation.ts

### MEDIUM Adding locale without updating supportedLngs

This is actually **not** an issue — the Vite plugin auto-detects locale files in `src/locales/`. Adding a new JSON file (e.g. `de.json`) is sufficient; `supportedLngs` is derived from `Object.keys(resources)` automatically.

Source: wcz-layout:src/providers/LayoutProvider.tsx

---

See also:

- skills/ui-pages/SKILL.md — Pages render inside the layout shell.
- skills/auth/SKILL.md — publicRoutes bypass auth; permission guards need auth context.
