Page recipes
The macro skeletons a route lives in — the vehicle for the app-wide page sweep. Where the workflow family is the chrome (TitleBar, SaveBar) and surfaces are the regions, recipes are whole-PAGE templates: each owns the layout + region hierarchy and takes ReactNode SLOTS (header/breadcrumbs/toolbar/content/aside/footer) so the app supplies the data. They compose the @phauna/ds page recipes (TablePage, RecordPage) with the STD workflow chrome over the Surface primitive. Index/Detail/Settings are server-safe; only ResourceEditPage is a client component (it owns the useUnsavedChanges route-leave guard). Flip the theme in the top bar to confirm every region re-skins from the tokens.
- ResourceIndexPage
- List/index template. Composes @phauna/ds TablePage: a full-width TitleBar (title + breadcrumbs slot + a 'New' primaryAction + secondaryActions) above a toolbar/filter band, then the table/grid slot (a ds/grid DataGrid or a passed table), an optional facet rail, and a pagination footer. Server-safe.
- ResourceDetailPage
- Record/detail template over @phauna/ds RecordPage: TitleBar header → a main content slot + an optional metadata aside (collapses beneath on narrow) → a provenance footer. maxWidth opts into a centered reading column. Server-safe.
- ResourceEditPage
- The canonical create/edit shell (client). RecordPage header + form slot (+ optional aside) closed by a dirty-aware workflow SaveBar; wires useUnsavedChanges(dirty) for the hard- + soft-nav leave guard. Parent owns form state and passes dirty/saving + onSave/onDiscard.
- SettingsPage
- Sectioned settings template: TitleBar over a responsive [nav rail | section stack] grid (the rail sticks on lg+, collapses on narrow), closed by a prop-driven SaveBar. Server-safe — drive dirty from your form; reach for ResourceEditPage if you want the route guard too.
- slots + RecipeHeader
- All four share RecipeHeaderProps (title / as / breadcrumbs / status / primaryAction / secondaryActions / actions, or a full `header` override) and the SaveBar-shaped props (dirty / saving / onSave / onDiscard / saveMessage / saveBar). Every region is a ReactNode — the template owns layout, the app owns content.
ResourceIndexPage
The 'here are all the X' route. A full-width TitleBar (breadcrumbs + a count badge + a 'New volunteer' primary and Export/Import secondaries that fold into a ⋮ on mobile) sits above a token-spaced toolbar (search + filters), the table, and a pagination footer. Pass a facets node to split a facet rail beside the table; pass your ds/grid DataGrid (or any table) into the table slot.
Volunteers
| Name | Team | ZIP | Status |
|---|---|---|---|
| Ada Okoro | March Organizers | 11215 | Active |
| Ben Castillo | Phonebank | 10027 | Active |
| Carla Devi | March Organizers | 11238 | Paused |
| Dan Whitmore | Canvass | 11201 | Active |
| Esi Mensah | Phonebank | 10001 | Active |
| Farid Khan | Canvass | 11217 | Pending |
- table (required)
- The data region — a ds/grid <DataGrid/>, a passed <table>, an <EmptyState/> or a <Skeleton/>. Lives in TablePage's table slot.
- toolbar / facets
- toolbar is the band above the table (search, bulk actions); facets is an optional rail (e.g. a <FacetRail/>) — when set the body splits at facetWidth (default 240px) and collapses on narrow screens.
- footer
- Pagination / counts, in a space-between foot band (small + muted). header props (title/breadcrumbs/primaryAction…) build the TitleBar above the recipe.
ResourceDetailPage
The 'view one X' route. The TitleBar carries the record's name + a status badge and the record actions (Edit primary; Message/Archive/Delete secondaries). The main body holds the record's sections; the metadata aside sits beside it (340px, collapsing beneath on narrow); a provenance line closes the page. Set maxWidth to cap it to a centered reading column.
Ada Okoro
About
- ada.okoro@example.org
- Phone
- (718) 555-0142
- Neighborhood
- Park Slope, Brooklyn
- Joined
- Mar 4, 2026
Recent activity
- Checked in — Spring Adoption Drive · 2d ago
- Joined team — March Organizers · 1w ago
- Completed onboarding · 3w ago
Record #4821 · last updated 2 days ago by Rebekah M.
- content (required) / aside
- content is the main body (RecordPage main); aside is the metadata/related rail — when set the body splits at asideWidth (default 340px) and stacks on narrow. footer is the provenance closer.
- maxWidth
- true → centers to var(--page-max); a CSS string → a custom reading width; omit for full-bleed. Same knob as the underlying RecordPage.
ResourceEditPage
The canonical create/edit shell. TitleBar (with a live Saved/Unsaved badge) over the form, an optional help aside, and the sticky SaveBar that rises from the bottom the moment the form is dirty — useUnsavedChanges is armed throughout, so reloading the tab while dirty prompts. The parent owns the form state and passes dirty / saving + onSave / onDiscard; the shell owns the chrome and the guard. (Demo: edit a field, then try to reload.)
Ada Okoro
Edit any field to dirty the form and raise the SaveBar.
- form (required) / aside
- form is the field stack (RecordPage main); aside is an optional help/metadata rail. maxWidth defaults to true (a centered reading column) — pass false (as the demo does) when an aside needs the room.
- dirty + SaveBar
- dirty drives the SaveBar visibility AND arms the route-leave guard. saving locks the bar + spins Save. The bar renders only when onSave/onDiscard (or a custom saveBar node) is given; saveMessage / saveBarChildren / saveLabel customise it.
- useUnsavedChanges
- Wired internally on `dirty` (this is why the shell is a client component). unsavedMessage sets the soft-nav confirm copy; guardEnabled is the master off-switch (e.g. flip false right after a successful save).
SettingsPage
The sectioned settings screen. A section-nav rail (sticky on lg+, collapsing above the sections on narrow) sits beside a stack of grouped settings cards, all under the TitleBar and closed by a SaveBar. The SaveBar is prop-driven here (the template stays server-safe) — drive dirty from your form; if you also want the route-leave guard, use ResourceEditPage or call useUnsavedChanges in your client parent.
Settings
Profile
Notifications
Privacy
Change any field to dirty the form and raise the SaveBar.
- nav / sections (required)
- nav is the section-rail (links/buttons); sections is the stacked groups. The grid splits at navWidth (default 240px); navEnd moves the rail to the trailing edge. Omit nav to render the sections full-width.
- SaveBar (prop-driven)
- Same SaveBar-shaped props as the edit shell (dirty / saving / onSave / onDiscard / saveMessage / saveBar) but WITHOUT the route guard, so SettingsPage stays server-safe. The bar renders only when there's a save path.