Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of New Page?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/robritacca-dotcom-new-page)
---
name: new-page
description: Add a new page to the website with the standard layout shell and correct navigation wiring. Use when asked to add or create a new page on the site.
icon: insert_drive_file
displayDescription: "Creates a new website page by mirroring a live exemplar page's layout shell, then wires it into every place the site tracks pages: the section sidebar and breadcrumbs, with the sitemap deriving automatically. Prevents the common mistake of adding a route without registering it."
invoke: ["add a page for [X]","create a [section] page","add [X] to the site"]
---
# new-page
Add a new page to the website with the standard layout shell and correct navigation wiring.
## When invoked
Use this skill when asked to add or create a new page on the website — phrases like "add a page for [X]", "create a [section] page", "add [X] to the site".
For a **component documentation page**, use the `component-doc-page` skill instead — it covers the variant showcase and component-specific registrations. For a **template screen**, use the `new-template` skill — it owns the family's composition conventions and the full registration checklist.
## Instructions
1. **Gather requirements** if not already provided:
- Page URL path (e.g. `/foundations/motion`)
- Which section it belongs to — the sidebar arrays in `website/src/config/navigation.ts` are the authoritative list of sections (components, foundations, the docs cluster, templates; standalone pages like `/playground` live in no sidebar array and declare their metadata as a literal). **Components are the exception**: `componentsSidebarLinks` is derived from `src/components/registry.json` (merged with the chat widget's `src/chat/registry.json` through `website/src/data/component-pages.ts`), so a component page is registered by adding a registry entry, not by editing the array — use the `component-doc-page` skill for those. Component categories have no pages of their own; a registry category entry becomes an index section and a sidebar accordion automatically. **`/blueprints/<slug>` pages are another exception**: each reads its body from a synced spec copy in `website/public/`, so a new one starts by registering the root spec (see step 4).
- Page title, a short `subDisplay` tagline, and a 1–2 sentence description (for metadata and the intro block)
- Storybook path (optional) — for `PageLinks`. A Figma link needs no URL: `PageLinks` reads the route's frame from `website/src/data/figma-nodes.json`
2. **Read the exemplars before writing anything.** The live pages are the source of truth for structure — mirror them rather than writing a shell from memory:
- `website/src/app/skills/page.tsx` — a standard content page (layout shell, sidebar wiring, header/intro blocks, entry animations)
- `website/src/app/components/button/page.tsx` + `page.module.css` + `layout.tsx` — the richest example, with `PageLinks` and per-page CSS
- `website/src/config/navigation.ts` — nav config (single source of truth for sidebars, mega menu, and breadcrumbs; the sitemap derives its routes from the sidebar configs)
3. **Create the directory** `website/src/app/<path>/` with three files, mirroring the exemplar:
### File 1: `page.tsx`
- Copy the exemplar's shell exactly — same components, same nesting, same class names. Don't improvise structure.
- Invariants the exemplar can't teach:
- `subDisplay` is a *tagline* inside the intro block (e.g. the Skills page's "Reusable AI instructions, tuned for this project") — not the section name; the breadcrumb already shows where you are
- All copy on the page (tagline, intro, body, metadata description) follows `content-design.md` — voice, register, and the words-to-avoid tables
- Sidebar links come from `getSidebarLinks(<section>SidebarLinks, "<your path>")`
- Include `PageLinks` only if the page has a Storybook path or a frame in `website/src/data/figma-nodes.json`
- **The on-this-page rail is automatic, not part of the shell.** `SiteAnchorRail` in the root layout reads every page's h2 headings after navigation and floats the rail on the right viewport edge, so a new page gets one with no wiring — never mount an anchor nav of your own by default. The gates live in `website/src/config/anchor-nav.ts`, whose doc block owns the rules: an index or landing page registers in `ANCHOR_NAV_EXCLUDED_ROUTES`; a page whose items must be derived server-side (markdown-extracted sections, labels that are not headings) mounts `FloatingAnchorNav` itself and registers in `ANCHOR_NAV_SELF_MANAGED_ROUTES` so it never carries two — the blueprints are the exemplar; and an immersive or chromeless surface also registers in `ANCHOR_NAV_EXCLUDED_ROUTES` even though chromeless routes are skipped anyway, so the intent survives if its chrome status ever changes (`/playground` and `/graph` are the precedent). Demo or furniture h2s that are not sections of the page sit inside `data-anchor-ignore` (or an `<aside>`); the discovery rules live in the doc block of `website/src/components/FloatingAnchorNav/SiteAnchorRail.tsx`
- **Do not render a background.** `BlurBackground` is mounted once in the root layout and covers every route; adding it per page would build a second canvas and a second GL context on top of the first. `scripts/validate-single-background-mount.mjs` fails the build if you do
### File 2: `page.module.css`
- Copy the exemplar's layout classes; add page-specific classes as needed
- Semantic design tokens only — no hardcoded colours or magic values
- Page assembly is a spec, not taste: design.md's **Composition** section owns the rhythm ladder and the assembly rules (parent owns spacing, one level of chrome, dividers last, prose never width-capped) — read it before laying out sections; the exemplar shows the pattern, the spec owns the rules
- No `ch`-based `max-width` on prose — doc paragraphs run the full content column; the layout column is the only width constraint (build-enforced by `scripts/validate-page-titles.mjs`)
- Mobile type and section rhythm collapse at the **token layer** (display sizes and section-gap tokens step down system-wide — `design.md`'s responsive spec owns the breakpoint) — do not add per-page `@media` overrides for tokenized values; when a page genuinely needs a breakpoint, use a width from `src/tokens/breakpoints.ts` (or the site families in `website/src/config/breakpoints.ts`); a content-driven width outside the set needs `/* ds-allow(breakpoint): <reason> */` directly above the query, or `scripts/validate-breakpoints.mjs` fails the build
### File 3: `layout.tsx`
- Sidebar-registered pages export `metadata` via the shared helper: `export const metadata = pageMetadata("<your path>", "<one-line description>")` (import from `@/config/navigation`)
- Standalone pages (`/playground`, `/graph` — pages in no sidebar array) export a literal `Metadata` object instead, with an explicit `alternates.canonical` — see `website/src/app/playground/layout.tsx`
- **Deliberately hidden pages are the exception to both rules** — and "hidden" has two independent halves. *Hidden from crawlers*: sets `robots: { index: false, follow: false }`, skips the canonical *and* the sitemap entirely, and records why in a comment beside that metadata. *Hidden from visitors*: appears in no nav surface. A page can take one without the other — the labs pages take both (`/labs/marketing` is the live example). Either way the page records why — in its `layout.tsx` (`/labs/marketing` is the precedent), or in the `page.tsx` itself when the page needs no layout of its own. A full-viewport or immersive page that should also render none of the shared chrome (the layout-mounted footer, chat panel, and command palette) additionally adds its route to `CHROMELESS_ROUTES` in `website/src/config/chromeless.ts` — the set in that file is the authoritative list of what has taken the exception. A hidden page also needs an entry in `EXCLUDED_ROUTES` in `scripts/generate-site-corpus.mjs` with a written reason, or `validate-chat-coverage.mjs` fails the build for an uncovered route. Suppressing chrome does **not** make the background full-bleed: by default every page gets the fixed-height band that fades into the page floor (the doc block of `website/src/components/BlurBackground/BlurBackground.tsx` owns the modes and the number). An immersive page must also render `<FullBleedBackground />` (exported from `website/src/components/BlurBackground/BlurBackground.tsx`), a hidden marker that CSS in `globals.css` reads to drop the fade and fill the viewport. Skip it and the page is chrome-free but band-limited, which looks wrong with nothing to explain why. The immersive stages take a third mode: a page whose panels float on the dotted working-canvas ground renders `<DotBackground />` (`website/src/components/DotBackground/`) beside `<HiddenBackground />` (exported from the same BlurBackground module), so the ambient layer stands down instead of glowing through the dots — `/playground` and `/graph` are the precedent (that doc block owns the background-mode contract). Pick exactly one: the default band, the home page's extended band (`<ExtendedBackground />`, same marker mechanism), full-bleed ambient, or hidden-plus-dots
- Only **component** pages are build-enforced (`scripts/validate-page-titles.mjs` requires `componentPageMetadata("<slug>")` there); for everything else the helper is convention, not a gate — follow it anyway
- Default export wraps `{children}` in a fragment
4. **Register the page everywhere the site tracks pages:**
- **Sidebar** (only if the page belongs to a section): add `{ href, label }` to the section's array in `website/src/config/navigation.ts`, matching that array's existing order convention (foundations and docs are curated in reading order; templates keep the index link first, then one entry per screen; components are derived from the registry — see the exception in step 1). A `/templates/<slug>` screen also renders full-viewport, so it takes a `CHROMELESS_ROUTES` entry and a corpus `EXCLUDED_ROUTES` reason — both covered in File 3
- **Blueprint pages are a second exception**: a `/blueprints/<slug>` page reads its body from `website/public/<name>.md` at build time via `fs.readFileSync`, and that copy only exists once the root spec is added to `FILES` in `scripts/sync-blueprints.mjs` (the authoritative list; `scripts/validate-website-surfaces.mjs` holds it to the pages in both directions, so a synced file without a page, or a page without a synced file, fails the build)
- **Standalone pages** (no sidebar section): no array to edit — but the sitemap then knows nothing about the route, so add it as a top-level literal in `website/src/app/sitemap.ts` (the `/playground` entry is the pattern), and if the page should be reachable from a header section's mega panel, add it to that section's `mega.groups` in `buildNavSections()` (same file) and widen the section's `isActive` predicate to match the new path — the drawer, footer, palette, breadcrumbs and sitemap all derive from `getNavSections()`/`getSectionItems()`, so a new item goes into a section's groups, never into a second list. If it is a top-level site page a visitor should reach from anywhere, also add it to the `siteLinks` array in `website/src/components/SiteFooter/SiteFooter.tsx` — the one footer column that is hand-maintained (of the other four, three derive from the nav config and Elsewhere from `PROJECT_LINKS` in `website/src/config/social.ts`) — and to the command palette's hand-written `navigation` group in `website/src/components/SitePalette/SitePaletteMount.tsx`, or the page is unreachable from Cmd+K (a docs-cluster page instead rides `docsSidebarLinks` automatically there; no validator covers either). Crawler-hidden pages (see File 3) never enter the sitemap; whether they enter the nav is a separate call — a fully hidden page registers nowhere, while a crawler-hidden page a visitor should still find (no live page currently takes this shape) takes the normal nav entries plus `desktopOnly: true` on its `MegaItem` when the page needs a pointer and a wide viewport: that flag hides the link at and below the tablet step (`BREAKPOINT_TABLET_PX` in `src/tokens/breakpoints.ts`, the number's one home; design.md's Responsive → Breakpoints section owns what the step does) on the surfaces that render at every width (the footer and the command palette), and the drawer omits desktop-only pages outright (`MegaNav.tsx` filters `desktopOnly` items out of each section's `drawerItems`, so the flag alone is enough)
- **Mega panel** for sidebar-registered pages: automatic for membership. The Docs and Foundations panels derive their groups from `docsSidebarLinks` and `foundationsSidebarLinks` (the Docs panel's Learn group takes every docs page no other group or the showcase claims), and the mobile drawer derives from the panels, so never add a hand list of hrefs to a panel group. The page does need an icon: an entry in `DOC_ITEM_ICONS` or `FOUNDATION_ICONS` in `website/src/config/navigation.ts` (the docs table also feeds the command palette's rows). `scripts/validate-nav-panels.mjs` fails the build on a missing or orphaned icon
- **Sitemap** for sidebar-registered pages: automatic — it derives from the sidebar configs, so the entry above covers it
- **Breadcrumbs**: sub-pages of an existing section resolve automatically from the sidebar entry. Only if the page starts a *new* section: add a `breadcrumbSections` entry, and a new `NavSection` in `buildNavSections()` — with its `isActive` predicate, and `mega.groups` if the section should open a panel. A standalone immersive page that mounts `StageToolbar` (whose trail comes from `getBreadcrumbs`) resolves through neither path: add a literal branch for it in `getBreadcrumbs` in `website/src/config/navigation.ts` — `/playground` and `/graph` are the precedent — or the toolbar renders an empty trail and nothing fails the build
- **The site-chat corpus has a ceiling, and a visible page counts against it**: `scripts/generate-site-corpus.mjs` extracts every indexed page's prose automatically, so there is nothing to register — but a substantial page can push the corpus past `TOKEN_BUDGET` and fail the build with an over-budget error before any validator runs. That constant's own doc block owns the call between raising the budget and trimming a section; read it, and run `node scripts/generate-site-corpus.mjs --sizes` to see which section actually grew before deciding
- **The AI-summary panel**: every page with the shared chrome gets the chat FAB, and hovering it opens a per-page summary with prompt chips. Add the page's entry to `website/src/data/page-summaries.json` (`routes` map: a `title`, a super-concise `text`, and 1 chip for a simple page, 2 for a dense one — the validator's field grammar is authoritative: text length and full stop, every chip carrying `id`/`label`/`prompt`, labels inside the suggestion budget, no em dashes), plus a `starters` pool: more questions than the chat's welcome screen draws (the validator reads the number), each `{ id, label }`, written about **this** page so the chat opens on its subject rather than its section's, answerable from the page's own prose (read it back in the corpus), and naming roles rather than theme values (`validate-chat-starters.mjs` fails a colour family or the base typeface) — `scripts/validate-page-summaries.mjs` fails the build for a route without one, so this cannot be skipped silently. Component pages derive theirs from the component registry and need nothing here; a chromeless page has no FAB and is exempt automatically
- **A drag grip**: a page that mounts something a visitor can drag (a split pane, a compare handle, a pannable map) adds an entry to `scripts/gestures.json` with the setup that reaches the grip, so the gesture stress check drives it in `verify` and CI (CLAUDE.md's Gestures row owns the registry). No validator notices a missing entry
5. **Verify**: load the page in the browser and confirm the sidebar highlights it, the breadcrumb trail is correct, and both themes render properly.