Atom · stable · v1.1.0 · owner: Site system lead · contract 19 of 20 points (open: figma)
Do and don't
Do: Honesty caption for sample data
Rendered by caption.tsOpen example: Caption · Do: Honesty caption for sample data Use the full-page example to follow page links.
Do: A data note with its file
Rendered by caption.tsOpen example: Caption · Do: A data note with its file Use the full-page example to follow page links.
Do: Native caption association
Rendered by caption.tsOpen example: Caption · Do: Native caption association Use the full-page example to follow page links.
Don't: A vague label
Rendered by caption.tsOpen example: Caption · Don't: A vague label Use the full-page example to follow page links.
Don't: Marketing in a caption
Rendered by caption.tsOpen example: Caption · Don't: Marketing in a caption Use the full-page example to follow page links.
Generated API variants, rendered live
Generated variants of the first authored example, from selected boolean and enum props. These are API explorations, not authored state declarations or verification results. Hover and focus respond to the pointer and the keyboard in the examples above.
Base authored example
Rendered by caption.tsOpen example: Caption · Do: Base authored example Use the full-page example to follow page links.
state: live
Rendered by caption.tsOpen example: Caption · Do: state: live Use the full-page example to follow page links.
state: mixed
Rendered by caption.tsOpen example: Caption · Do: state: mixed Use the full-page example to follow page links.
state: dev
Rendered by caption.tsOpen example: Caption · Do: state: dev Use the full-page example to follow page links.
Rules
| Rule | Why | Check |
|---|---|---|
| Native IDs/classes belong to the caller; source language does not translate content or add live announcements. | Compatibility retains existing semantics. | Automated: tests/unit/frame-api-compatibility.test.ts |
| Every figure, screenshot and showcase has a caption. | A picture without its source cannot be checked, and the site promises everything can be. | Automated: scripts/site/system/compose.ts (figure without figcaption) |
| Sample, synthetic or recorded data says so in the caption. | The reader must never mistake a demo for a client's live data (AGR-0001 truthfulness). | Automated: site-claims test and the honesty-caption content tokens |
| One caption component; no element defines its own caption style. | Fifteen caption styles made the site look unplanned (inventory, 26 Oct). | Automated: scripts/site/system/check.ts (caption classes owned only by caption and figure elements) |
API
| Prop | Type | Required | Default | What it does |
|---|---|---|---|---|
id | string | No | Caller-owned native caption ID; no generated IDs. | |
lang | en | ja | No | Explicit source language. | |
extraClass | string | No | Existing native component caption classes, retaining their owning styles. | |
html | html | Yes | What it shows and where the data comes from; may contain a text link to the data. | |
kind | default | honesty | source | data | No | "default" | Default for figures, honesty for sample data, source for a cited source, data for a data note. |
label | string | No | A bold lead such as 'Data:'. | |
claim | string | No | The claims-register id the honesty caption speaks for (data-claim). | |
state | live | mixed | dev | No | The claim's state, for the honesty variant. | |
tag | p | figcaption | span | No | "p" | figcaption inside a figure. |
Contract details
The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests
Identity
| Aspect | Value |
|---|---|
| Id | caption |
| Layer | Atoms |
| Category | typography |
| Owner | Site system lead |
| Status | stable |
| Version | 1.1.0 |
| Source | apps/showcase/site/system/atoms/caption.ts (private repository; the contract and the render function are this one module) |
| Version | Date | Change |
|---|---|---|
| 1.1.0 | 2026-10-29 | Add native caption identity and source-language forwarding; defaults unchanged. |
| 1.0.0 | 2026-10-26 | One caption for every figure, showcase and data line, with an honesty variant for sample data. |
Use when
- Under every figure, diagram, screenshot and showcase. Why: A picture without its source is a claim nobody can check.
- When data is sample or synthetic. Why: The honesty variant says so in the same place every time (D-SITE-007).
Do not use when
- For body copy. Why: Captions are small; the argument belongs in the text.
- To hide a limitation. Why: Limits are stated plainly; a caption never softens them.
Anatomy
| Part | What it is |
|---|---|
| root | figcaption, p.data-note, p.source or span.demo-source. |
| label | An optional bold lead ('Data:'). |
| text | What it shows and where the data comes from. |
Made of: tokens:type, tokens:colour, text-link.
Used in: figure-frame, stat, academy-lesson, agent-check, autonomy-demo, before-after, cloth-diagram, conformance-table, contract-excerpt, conversations, cost-calculator, coverage-map, decision-list, design-md-excerpt, drift-hero, drift-palette, first-90, flow-diagram, flow-layers, identity-motion, layers-demo, ops-board, product-screen, proof-shift, read-your-stylesheet, replay, repo-change, repo-tree, roles-picker, score-us, showcase-frame, stage-finder, system-of-systems, takenote-card, token-chain, problem-section.
Variants
| Variant | When | Why |
|---|---|---|
| default | A figure's description. | Small, secondary ink, under the figure. |
| honesty | Sample, synthetic or recorded data. | Bold, so the reader cannot miss that the data is not live. |
| source | A cited study or report. | The reader can find the source. |
| data | A data file behind a page. | The file is named and linked. |
States
| State | Rendered or not applicable |
|---|---|
| default | default |
| hover | Not applicable: A caption is not interactive, so it has no hover state. |
| focus-visible | Not applicable: A caption does not take focus; links and buttons inside it carry their own focus ring. |
| active | Not applicable: A caption cannot be pressed. |
| disabled | Not applicable: A caption has nothing to disable. |
| loading | Not applicable: A caption is rendered at build time and is complete when the page loads. |
| empty | Not applicable: A caption is not rendered without content; the builder skips it. |
| error | Not applicable: A caption shows no data that can fail. |
| current | Not applicable: A caption has no current item. |
| visited | Not applicable: A caption is not a link. |
| expanded | Not applicable: A caption does not open or close. |
Tokens
| Part | Token |
|---|---|
| root.font | --type-small (honesty: --type-small-strong) |
| root.color | --ink-2; --field-fg-2 on a field |
| --caption-font-type-small | var(--type-small) |
| --caption-color-ink-2 | var(--ink-2) |
| --caption-color-ink | var(--ink) |
| --caption-font-type-small-strong | var(--type-small-strong) |
| --caption-font-size-fs-small | var(--fs-small) |
| --caption-margin-top-space-5 | var(--space-5) |
Layout
| Aspect | Value |
|---|---|
| max | 60 characters |
| breakpoints | Required review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; same everywhere. |
| wrapping | Wraps; long file names break anywhere. |
| textExpansion | Grows downwards; captions have no fixed height. |
Spacing and rhythm
| Aspect | Value |
|---|---|
| internal | None. |
| external | --space-4 below its figure. |
| rhythm | Always directly after the figure it describes. |
Motion
| Aspect | Value |
|---|---|
| None | Static text. |
Runtime and reuse
Render-only element; mount any registered child enhancements separately. Preserve required Site CSS, fonts, responsive and locale rules from nativeElement.
Accessibility passport
| Aspect | Value |
|---|---|
| role | figcaption gives the figure its description; otherwise a paragraph |
| name | Its text |
| keyboard | Not interactive (links inside are). |
| focus | Not focusable. |
| screenReader | Read after the figure; the figure is labelled by it. |
| contrast | --ink-2 on cotton 8.6:1; field secondary text 4.5:1 or better. |
| target | Not a target. |
Content
| Slot | Length | Voice |
|---|---|---|
| html | 2 sentences | What it shows, then where the data comes from: 'Sample data', 'Recorded server output', 'Real audit output' |
Good: “Real audit output, scored with Loom's own open rubric.” · “Sample data. The organisation and its rules are made up.”
Bad: “Illustrative” · “Powered by AI”
Japanese: The existing caption.sample, caption.scripted and caption.real-output tokens have Japanese AI drafts pending native review. Recorded-source captions retain their consuming element's wording; they are not relabelled as scripted examples.
Icons and illustrations
None: this element uses no icon or illustration of its own.
Decisions and exceptions
- D-SITE-007: One caption, with an honesty variant
- EX-SITE-001: The interactive organisms keep their own caption classes (ix-*-caption) until they are moved into the ShowcaseFrame caption. Owner: Site system lead; expires 2026-11-30.
Usage
29 uses across the built pages (measured by npm run site:system).
Tests
| Aspect | Value |
|---|---|
| unit | tests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns |
| visual | Not yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts |
| a11y | tests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark |
| parity | scripts/site/system/check.ts: Library examples are rendered by the same render function the site imports |
Figma
not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.