Docs site
This site is a pattern, not a bespoke build: the chassis lives in @gridd/ui, and a new docs site — an API reference, an operator handbook — is an app.config and a folder of pages.
What it is made of
- The
docslayout — navbar, nav rail, drawer below 1200px, ⌘K palette, theme control, and the app-frame scroll contract. GdDocsPage— the full-bleed page header band plus the body column, and the contents rail — which it reads off the sections in its own slot, so there is no second list to keep in step.GdDocsSection— an anchored section: heading, lede, prose at the measure.GdDocsSpecimen— the live-example frame, with its snippet one click under it.GdDocsSearch— the palette. The nav indexes every page for free;searchExtrasadds anchors and the words people actually type.GdSideNav— the tree rail, at whatever width the nav's labels need.- Head hygiene — the standfirst becomes the meta description, the theme is stamped before first paint, and the layout links
/favicon.svg.
The block
1 · Declare the content
// app/app.config.ts
export default defineAppConfig({
griddDocs: {
brand: "API", // the wordmark suffix
siteTitle: "GRIDD API Reference", // the <title> suffix
nav: NAV, // GdNavSection[] — sections, items, children
searchExtras: SEARCH_EXTRAS // GdDocsSearchEntry[] — in-page anchors + synonyms
}
})2 · Opt into the layout
<!-- app/app.vue -->
<template>
<div>
<a class="gd-skip-link" href="#content">Skip to content</a>
<NuxtLayout name="docs">
<NuxtPage />
</NuxtLayout>
</div>
</template>3 · Write pages
<!-- app/pages/endpoints/parcels.vue -->
<GdDocsPage eyebrow="Endpoints" title="Parcels" standfirst="…">
<GdDocsSection id="request" title="Request">
<p>Prose at the measure.</p>
<GdDocsSpecimen label="…" note="…" code="…">
<!-- live components -->
</GdDocsSpecimen>
</GdDocsSection>
</GdDocsPage>The specimen frame, live
Code
<GdDocsSpecimen
label="Sizes"
note="A caption under the frame — a label, a note, or both."
code="<GdButton size="sm">Small</GdButton>"
>
<GdButton size="sm">Small</GdButton>
<GdButton size="md">Medium</GdButton>
</GdDocsSpecimen>Rules
- List every page in the nav.
GdNavSection[]drives the rail, the drawer, the search index and the prerender crawl at once — a page missing from it is a page that does not exist. - Give every page a standfirst. It is the meta description as well as the first line a reader gets.
- Give every specimen a
codeprop. A specimen without one documents something a reader cannot use. - Ship
/favicon.svgfrom the app. The chassis links it and does not provide it. - Add anchors and synonyms via
searchExtras— the words people type are rarely the words in the heading. - Do not hand-roll a second docs chrome. The layout, the rail, the palette and the frame are the block.
Behavior & Anatomy
The nav is the sitemap
Prerendering crawls links, so listing a route is what publishes it. GdNavSection[] drives the rail, the drawer, the search index and the crawl at once, and there is no second list in nuxt.config.ts to forget.
The behaviours, and what each one cost
- App-frame scrolling — at 1200px and up the document never scrolls; the middle column does, and the rails hold still.
- A contents rail that tracks reading — computed from scroll position against a reading line, not observed. An
IntersectionObserverneeds a threshold that is right for both a two-line section and a screen-tall one, and there is no such threshold; “is this heading's top above the reading line” is unambiguous at every scroll position, including at the very bottom, where nothing new can intersect. - A breakpoint that is watched, not assumed — crossing up closes the drawer instead of stranding it over the rail.
- ⌘K anywhere — the palette listens on
document, and the rail's search control is the visible way in for anyone who was not told about the shortcut. - Three-state theme — system by default; light and dark pin
data-gd-themeand native chrome follows viacolor-scheme. The stamp happens in<head>, before first paint, because a toggle mounted in the body is already too late and the flash only shows up on refresh. - One grid —
--gd-docs-columnand--gd-docs-gutterput the header's words, the prose and the specimens on the same left edge.
Why the snippet is collapsed
GdDocsSpecimen's code prop renders as a <details> under the frame, closed. Fifty expanded code blocks would double the length of a site whose reported problem is that it is hard to follow; collapsed, the snippet is one predictable click from the specimen it belongs to, always in the same place. It is a real <details> rather than a bound v-if so the browser supplies the disclosure semantics and the keyboard — and, on a prerendered site, so the content is in the DOM whether it is open or not, which is what lets ⌘F and the crawler find it while closed.
Live is the point
The thing in a specimen frame is the real component from @gridd/ui, rendered by the page. A screenshot, or a re-implementation with the same CSS, would document a component that does not exist — and would keep documenting it after the real one changed.