# Demo authoring guide
This document is the canonical reference for authoring component demo
pages under `web/routes/uix/components/{name}/+page.svelte`. The
template is **locked** — every demo must follow it. Per-component
creativity is forbidden; the only thing that varies is the data the
component declares.
The canary template is the **drawer** demo —
`web/routes/uix/components/drawer/+page.svelte`. When in doubt, copy
from drawer and adapt.
## 1. File location + frame
Every demo lives at `web/routes/uix/components/{kebab}/+page.svelte`.
The file is wrapped by `web/routes/uix/+layout@.svelte` (the `@`
resets intermediate layouts) so the chrome — top bar, sidebar rail,
canvas frame — is provided. The demo only renders the canvas inner.
The outermost element of every demo is:
```svelte
…
```
## 2. Required imports
```ts
import {
X,
type XSize,
type XVariant,
// …other public types
} from '$uix/eidos/components/{name}';
import { compileMorfo } from '$uix/morfo';
import { xMorfo } from '@/uix/morfo/components/{name}';
import { getActiveUix } from '$active-uix';
const uix = getActiveUix();
```
Multi-instance components (toast) also import `createToaster` (or the
analogous instance constructor). Eidos-only components (icon, avatar)
omit `getActiveUix` since they have no Sema tab.
## 3. Tabs
Six tabs in this order:
```
Live · API · [morfo badge] Np·Me · [sema badge] N · Recipe · A11y
```
The Sema tab is **dropped** when the morfo declares 0 events
(tooltip, icon, avatar). The badge counters use `partsList.length`
and `events.length`.
Tab union:
```ts
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state('live');
```
There is **no** `Translates` tab and **no** `Anatomy` tab — both were
inventions that have been removed.
## 4. Header
```svelte
{Category} · {Name}
{Name}
{one-paragraph summary — what the component does, how many parts
and events, anything notable about its semantic shape}
parts{compiled.parts.order.length}
events{events.length}
```
## 5. Live preview is always rendered
The stage sits **between** the header and the tablist, not inside the
Live tab. This is critical so that:
- The Sema tab's `▶ play` buttons can fire on a real instance.
- The trace strip stays visible across tab switches.
```svelte
…
trace
{#if trace.length === 0}
{instructional copy — "open the X to see events"}
{:else}
{#each trace.slice(0, 3) as entry}
{entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}
{fmtTime(entry.at)}
{/each}
{/if}
{state-key} {String(stateValue)}
```
## 6. MutationObserver trace
Watch `data-event` on the stage subtree. The runtime stamps it via
the sema engine before each event fires:
```ts
type TraceEntry = { event: string; family: string; intent?: string; at: number };
let trace = $state([]);
let stageRef = $state(null);
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{
event: ev,
family: target.getAttribute('data-event-family') ?? '—',
intent: target.getAttribute('data-event-intent') ?? undefined,
at: Date.now()
},
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes: true, subtree: true, attributeFilter: ['data-event'] });
return () => obs.disconnect();
});
```
For multi-instance components (toast Item) the observer still works:
each Item gets its own stamps because the runtime is per-instance.
## 7. Live tab — per-architectural-layer subsections
The Live tab groups controls by the layer that owns them. **There is
no `[sema]` subsection in Live** — sema events live in their dedicated
Sema tab.
```
Controls
── intro paragraph: layer-grouped, links to Sema tab for events
── [soma] props · headless behavior (subsection)
── [eidos] props · visual treatment (subsection)
── (additional groups: Group / Item / Demo content / Header etc.)
── [soma] somaSnippet pre/code (reactive $derived)
── [eidos] eidosSnippet pre/code (reactive $derived)
```
Subsection headings carry layer badges:
```svelte
soma props · headless behavior
```
Both code snippets are at the bottom of the Live tab, in this order:
soma first, eidos second. Each in its own `data-uix-code` block with
`data-uix-code-head` showing the layer badge + a one-line caption +
language tag.
```svelte
soma
headless · ARIA + behavior only
svelte
{somaSnippet}
```
## 8. Snippet derivation pattern
```ts
const somaSnippet = $derived(
[
"