Skip to content
Blocks

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 docs layout — 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; searchExtras adds 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

A specimen, inside a specimenThe frame documenting itself: the caption below, and the snippet under the disclosure — this is the real GdDocsSpecimen, not a picture of one.
Code
<GdDocsSpecimen
  label="Sizes"
  note="A caption under the frame — a label, a note, or both."
  code="<GdButton size=&quot;sm&quot;>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 code prop. A specimen without one documents something a reader cannot use.
  • Ship /favicon.svg from 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 IntersectionObserver needs 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-theme and native chrome follows via color-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-column and --gd-docs-gutter put 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.

Navigate

Esc