docs · standard
demo-standard — one page skeleton, derived from the market leaders
The market research (.agents/audit/2026-08-30-site-walkthrough) is unambiguous: the leading registries win on demo-page CONSISTENCY, not component count. This page is the law every component page follows — and the lint that keeps it true.
install
npx jixoai-ui add select — or point shadcn add at the item URL: https://ui.jixoai.com/r/select.json
$npx jixoai-ui add select
https://ui.jixoai.com/r/select.json
usage
Usage
The skeleton assembles from page-owned chrome (DocsInstall, DocsSeeAlso) and the existing canvas + section components — nothing new to invent per page.
<!-- the page assembles (select is the exhibit) -->
<!-- every /docs/components/<name>.html page, top to bottom -->
<DocsInstall name="select" /> <!-- Install: npx + registry URL -->
<SectionCard family="usage" title="Usage"> <!-- Usage: minimal working example -->
<CodeBlock code={usage} lang="svelte" />
</SectionCard>
<ComponentCanvas title="select" …> <!-- Examples: ability-named demos -->
…demos, each with collapsible code…
</ComponentCanvas>
<PropsTable meta={selectMeta} docs={SELECT_DOCS} /> <!-- API: one source -->
<DocsSeeAlso name="select" /> <!-- See Also: reading-chain data -->law
The skeleton is law
Every /docs/components/<name>.html page presents six sections, in this order. Page-local sections (types, a11y, theming…) may interleave — the six keep their relative order.
Keyboard
| Key | Action |
|---|---|
| Intro | The one-paragraph contract — hero SectionCard, exactly one h1, pills for the capability surface |
| Install | Copy-ready `npx jixoai-ui add <name>` + the registry item URL (https://ui.jixoai.com/r/<name>.json) — the shadcn add path for non-CLI consumers |
| Usage | The minimal working example — one code block, the smallest honest mount |
| Examples | Ability-named demos on the component canvas, each with collapsible code (the drawer shows the exact registry copy) |
| API | The props table — Prop / Type / Default / Description, rendered from the generated meta (one source) |
| See Also | Related component links derived from the docs reading chain — data, never a hand list |
ARIA
| Attribute | Value | Description |
|---|---|---|
| data-doc-install | section root | The lint’s Install marker; the section text carries the command verbatim |
| data-doc-props-table | table element | The lint’s API marker — PropsTable stamps it itself |
| data-doc-see-also | section root | The lint’s See Also marker; must hold ≥1 link to another component page |
grammar
Demos are named by ability
One phrase names one capability — what the demo LETS YOU DO, never a number, never a scene noun, never two capabilities at once.
yes — the ability
with clear button
async loading
multiple chips
with descriptions
rtl geometryno — anything else
demo 1 <!-- numbered: says nothing -->
example-two <!-- numbered + kebab: worse */
login scene <!-- a scene noun, not a capability -->
fancy <!-- an opinion, not an ability -->
with clear button and async loading <!-- TWO capabilities in one name -->The lint enforces the mechanical subset (numbered titles fail); the semantic half — one phrase, one capability — is review law. When a demo needs a second capability, it needs a second demo.
convention
Promoted variants take the suffix
A demo that ships as its own registry item is <name>-<ability>: the composition, standalone, named after the ability it grants (the @reui/date-picker-range precedent).
// registry.json — a promoted demo becomes its own item with the
// <name>-<ability> suffix (the @reui/date-picker-range precedent):
{
"name": "date-picker-range",
"type": "registry:ui",
"files": [ /* the range-mode composition, standalone */ ]
}The suffix is the demo name, kebab-cased: with clear button promotes to select-with-clear-button. The page-level demo
and the registry-level variant share one ability vocabulary — there is no third naming
scheme.
one source
Props tables read the generated meta
Name, type and default come from the component's GENERATED .meta.ts (component-metadata-gen, drift-gated); the page carries only the docs curation — prose, flags, and corrections where the extractor's documented ceiling bites.
The curation layer is accountable: the drift lock
(test/props-table-meta-drift.spec.ts) pins which override
fields each pilot uses, so a new correction can only land as a conscious edit. As the
extractor grows (JSDoc descriptions, $bindable() flags, ? optionality, alias-to-union merging), the curation
shrinks — the table never silently disagrees with the interface again. The API section
below renders from the select meta, live:
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| options * | SelectOption[] | — | The full option list; order = panel order. |
value bind | string | — | Committed value; undefined shows the placeholder. |
| placeholder | string | 'Select...' | Trigger text when nothing is selected. |
| label | string | — | Field label rendered as label[for] above the trigger. |
| name | string | — | Form field name — the bridge submits the committed value under it. |
| error | string | — | Error text: sets aria-invalid, wires aria-describedby, dashes the shell. |
| multiple | boolean | false | Reserved extension direction — not implemented in v1 (warns). |
| variant | 'solid' | 'acrylic' | 'auto' | 'auto' · Own default, not ambient | Floating-surface fill of the panel. |
| onchange | (value: string) => void | — | Commit hook (issue #6): fires with the newly committed value on every commit route (click, Enter, Space), alongside the bind:value write — sugar over bind:value. The native change event never fires on the trigger button, so this prop owns the channel outright. |
| disabled | boolean | false | Disables the trigger and the form-bridge field. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |
adoption
Staged, not partial
The skeleton lint hard-fails every in-scope route and warns on the rest while printing the backlog. The scope file is data; the successor change flips the gate global when the backlog is empty.
// scripts/docs-skeleton-scope.json — the staged lint's data
{
"version": 1,
"successor": "2026-08-30-docs-demo-standard-global-gate",
"inScope": ["select", "popover", "checkbox", "card-grid",
"date-picker", "toast", "combobox"],
"backlog": [
{ "route": "press-button", "owner": "2026-08-30-canvas-floor-lab" },
{ "route": "table", "owner": "2026-08-30-table-grid-toolbar" }
// …every route lands here with its owning change before the flip
]
}An out-of-scope page missing sections is BACKLOG, not violation — the gate stays green while the migration proceeds page by page. When the successor lands, the warn list must be empty or the flip fails: the exit criterion is computed, never asserted.
worked example
Adding a demo, end to end
The select page's with-descriptions demo, walked through the standard: name it by ability, show it on the canvas with collapsible code, and let the props table pick up any new prop from the regenerated meta.
Keyboard
| Key | Action |
|---|---|
| 1 · name | “rows with descriptions” — the ability is per-option second lines; one phrase, one prop (the options array’s description field) |
| 2 · place | A demo section under the canvas — after Usage, before API; the drawer shows the exact registry copy (?raw, the same-source law) |
| 3 · props | A new interface prop regenerates the meta (component-metadata-gen) — the table row appears; only its prose lands in the docs curation |
| 4 · gate | verify:docs-structure hard-fails the page if a skeleton section goes missing; verify:meta fails if the meta forgets the regeneration |
ARIA
| Attribute | Value | Description |
|---|---|---|
| lint rule | numbered titles | “demo 1” / “example 2” names fail the skeleton lint on in-scope routes |
| lint rule | section order | intro < install < usage < examples < api < see-also, by first marker position |