Design-Console Components
The three types that document a design system rather than build a feature — specimen, field-specimen and preview.
Three components exist to document a design system. Sovrium's own design-system console is built from them, and your app can build its own page the same way.
They read your design rather than repeating it, which is the whole point. A page that writes out its own copy of a button shows what that button looked like the day it was typed. A specimen draws the one your app renders now.
The design tokens themselves are drawn by ordinary kit types: swatch paints a colour token and plots an easing one, badge with variant: contrast grades a pair for legibility, and card with variant: scoped marks a design boundary on a page showing two systems at once.
specimen
Draws one component beside the config literal that produced it. Both are projected from the same declaration, so the snippet cannot stop matching the thing above it.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
showSnippet |
boolean | Show the config literal beside the drawing, projected from the same declaration so the two cannot drift | ||
showProvenance |
boolean | Show per-part badges naming which layer contributed each class — Sovrium's recipe, your design.components block, or the locked accessibility floor |
subject
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
subject |
object | A component type the engine draws its own catalogue specimen for — the alternative to writing the component out | ||
subject.type |
string | The component type to draw, using the engine’s own catalogue specimen for it. A catalogued type name; $param.<name> naming a segment of the host page’s path; or $record.<field> naming a column of the row this specimen is expanded from, which requires a record-binding ancestor. Mutually exclusive with component. |
||
subject.component |
string | A reusable template from components[] to draw on its own, by name — the operator’s own component rather than an engine type. Under an admin mount the name resolves against the documented app’s templates. Mutually exclusive with type. |
||
subject.variant |
string | The variant to draw the subject in. A member of that type’s own variant axis, or a $record./$param. reference resolved per row. |
||
subject.size |
string | The size to draw the subject in. A member of that type’s own size axis, or a $record./$param. reference resolved per row. |
||
subject.state |
string | The state to draw the subject in — a state its CATEGORY draws. A rendered state is reached through a real attribute; a depicted one is painted, because a browser pseudo-class cannot be forced from markup. |
viewport
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
viewport |
object | The viewport a specimen is re-rendered at, in a document of its own | ||
viewport.width |
number | The CSS pixel width of the document the specimen is re-rendered in. This is a real viewport, so the subject’s own media queries evaluate against it — a subject declaring lg: rules collapses below 1024 here exactly as it does in a browser window of this width. |
annotations
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
annotations |
array | Callouts naming the parts of the drawn component | ||
annotations[] |
object (truncated) | A callout naming one part of the drawn component |
annotations are callouts naming parts of the drawn component, [{ part, label }], using the same part names a design's component block uses. showProvenance shows, per part, which layer contributed each class — the recipe, your own component block, or the locked accessibility floor.
components:
- type: specimen
showSnippet: true
showProvenance: true
annotations:
- { part: root, label: The button itself }
component:
type: button
label: Save changesNaming a subject instead of writing one out
component holds a literal, which is what makes the snippet honest — and also means one declaration draws one type. A per-type kit route would need one page per type. So a specimen may instead NAME its subject, and Sovrium draws its own catalogue specimen for that type, with the illustrative props each type needs to be a specimen OF something. Rendered bare, a select is an empty box; the catalogue is where that knowledge lives.
pages:
- path: /kit/:type
components:
- type: specimen
subject: { type: $param.type }
showSnippet: truesubject.type is either a catalogued type name (button) or $param.<name> naming a segment of the host page's path. A literal is checked when the config is read: a typo, or a type a specimen may never draw, is named at boot. A $param one is a URL segment rather than a config fact, so a segment naming nothing drawable answers 404 — a mistyped URL that silently rendered some page makes a broken link look live, and an empty frame leaves a reader believing the type has no specimen rather than no existence.
component and subject are mutually exclusive, and exactly one is required. Both answer "what is drawn", and there is no defensible precedence between them: a specimen declaring both would show one and silently discard the other, along with the snippet projected from it.
A specimen refuses to draw a component that renders a submit control — form — at any depth, and refuses to nest inside another specimen. The first is a safety rule: a preview frame carries no write path. The second is a bound: each level projects its snippet from the level below, so the nesting would have no end. Both are refused when the config is read, so the failure names the reason instead of silently drawing nothing.
field-specimen
Draws the control a table field type gets: one column type, rendered as the form control your record form will actually show for it. Where specimen documents the component half of a design system, this documents the data half.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
fieldType |
string | The table field type to draw a control for. A catalogued field-type name; $param.<name> naming a segment of the host page’s path; or $record.<field> naming a column of the row this specimen is expanded from, which requires a record-binding ancestor. |
||
name |
string | The name attribute the drawn control carries — the element a comparison against a real form is anchored on. Defaults to the field type with hyphens replaced by underscores, which is what FieldNameSchema accepts. |
||
label |
string | Visible label on the drawn control (default: the humanised control name) | ||
placeholder |
string | Placeholder text inside the drawn control | ||
description |
string | Helper text under the drawn control | ||
value |
string | An illustrative value in the drawn control. Empty by default — a specimen documents the control, not a record. | ||
compact |
boolean | Suppress this control’s own surface caption, for a form that states it once above the whole group. Never suppresses the deferred-fidelity marker. |
options
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
options |
array | Option values for a choice-shaped field type. Absent draws the empty control — which is what an author who declares no options actually gets. | ||
options[] |
string | One option value the drawn control offers |
fieldType is required and takes a catalogued field-type name, $param.<name> naming a path segment, or $record.<field> naming a column of the row the specimen is expanded from. name defaults to the field type with hyphens replaced by underscores, and label to the humanised control name. options absent draws the empty control, which is what an author who declares none gets. value is empty by default: a specimen documents the control, not a record.
components:
- type: field-specimen
fieldType: single-select
label: Status
options: [Draft, Sent, Paid]It documents one surface, and says which
A field type is drawn on more than one surface, and those surfaces legitimately differ. So a specimen cannot claim to show the rendering of a field type. It shows one named surface's control, and carries a caption saying which. compact suppresses that caption for a page drawing every type at once, where the same sentence belongs once above the group rather than dozens of times inside it.
Where a surface has no exact control yet, the specimen reports itself as deferred rather than drawing an approximation and letting you believe it. compact never suppresses that marker: hiding a caption is a layout choice, and hiding a fidelity warning is not.
It hosts a control, and offers no way to submit it
The drawn control is a real one: the same control your record form shows, not a lookalike built beside it, which is what keeps the page from drifting away from the form. It is hosted directly, with no form around it and no submit affordance, so anyone who can read the page cannot become an accidental editor of anything.
There is no surface property and no fullWidth. The first would ask you to spell a constant — there is one surface today, and the property appears on the day there is a second. The second is layout, and layout belongs to the container you put the specimen in, not to a fact about the field type.
preview
Draws one option of one type, set to the value you name — so a setting can be seen rather than described.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
caption |
string | One line under the drawing saying what this value does. Ordinary text, so $record.<field> resolves here. |
||
showValue |
boolean | Print option: value above the drawing (default: true). Turn it off where the surrounding layout already names the option. |
subject
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
subject |
object | The type, the option and the value a preview draws | ||
subject.type |
string | The component type to draw. A catalogued type name; $param.<name> naming a segment of the host page’s path; or $record.<field> naming a column of the row this preview is expanded from. |
||
subject.option |
string | The option to set, in the path grammar GET /api/admin/schema/component-types/:type/options publishes — dotted, with [] for an array level. $record.<field> resolves it per row. |
||
subject.value |
string | number | boolean | The value to set the option to. A string is coerced against the option’s own kind, so a $record.value row template and a literal draw the same thing. |
All three parts of subject are required. subject.type is a catalogued type name, or $param.<name> naming a segment of the host page's path, or $record.<field> naming a column of the row the preview is expanded from. subject.option is a path in the dotted grammar the component-type options endpoint publishes, with [] for an array level. subject.value is a string, a number or a boolean; a string is coerced against the option's own kind, so a row template carrying $record.value draws the same thing a literal 2 would.
components:
- type: preview
subject: { type: table, option: pagination.position, value: both }
caption: 'Pagers above and below, for a grid taller than the viewport.'
- type: preview
subject: { type: table, option: striped, value: true }
showValue: falseIt draws one value, not a variant
specimen draws a component in a state — a variant, a size, a resting or hovered appearance — and its axis fields are genuinely optional, because a type has a default variant and a default size. A preview draws one option at one value, and no option has a "default option": that is why all three parts of subject are required rather than optional.
A structured value is deliberately out. An option whose value is an object is not one a reader learns from a single picture, and admitting one would make the caption unwritable. The empty string, by contrast, is deliberately in: a real option's interesting value is sometimes '' — an empty placeholder, an empty emptyText — and a picture of it is exactly what a reader needs.
The caption is written, not generated
A schema description says what an option is. What a reader needs beside the picture is what this value does to it, and that is a sentence only an author can write. Omit caption where the effect is self-evident; Sovrium's own console writes one per row, which is a fair guide to how often it is not.
showValue is on by default, and the console turns it off because each of its Configuration rows already prints the option path in its own left column. An app drawing a single preview on its own page leaves it on.
Behaviour
one option, at one value, as a drawing
- Draws the named type with the named option set to the named value
- Applies a boolean and a numeric option value
- Prints the option and its value unless told not to
- Draws an authored caption under the drawing
- Refuses at boot a subject that carries a write path
- Resolves a subject deferred to the route
- Refuses at boot an option path the subject type does not publish
- Draws nothing when a routed option aims at the key the renderer dispatches on
- Rollup consolidating the six criteria into one workflow: a Configuration section draws one option at several values
Showing the Curves an App Moves On
- Each curve should be drawn from its own control points, so four inherited curves draw four shapes
- The resolved
cubic-bezier(...)should be printed only when it is asked for - A token that names no curve should be refused at BOOT, and one that does should boot
Showing What Each Field Type Looks Like
- The control a field type gets should be drawn, anchored on a name derived from the type
- Each field type should be drawn with the same control the real crud-form draws
- The control should be hosted directly, with no form and no way to submit it
- A DEFERRED control should be reported as deferred rather than claiming exactness
- The per-control caption should be suppressed under
compact, and the fidelity marker never is
Documenting a Design System in Config
- A
swatchshould print a token’s value in both notations, resolved from the ramp - A swatch should print NEITHER notation unless it is asked for
- A
source: cascadeswatch paints its OWN node, from the live variable - A
specimenshould draw the component AND the literal that produced it, from one declaration - A specimen should attribute each part’s classes to the layer that contributed them
- A specimen carrying a write path should be refused at BOOT, naming the decision
- The same refusal should apply to a write path nested inside the drawn component
- The same page should boot when the specimen draws a type it MAY draw
- A
carddeclaringvariant: scopedshould scope the design to itself, and no other card should - A
carddeclaringvariant: scopedshould be a BARE boundary, carrying none of the card surface - A
carddeclaringvariant: specimenshould stage its children and pass its authored identity through untouched - A
badgedeclaringvariant: contrastshould state the ratio and the verdict, and no other badge should be graded - A
swatchshould draw a token, plotting an easing one as a static SVG that mounts no island
A Card Index Built From One Declaration
- One specimen declaration should draw one specimen per row, of the type its own row names
- A
$record.reference with no row to resolve it should be refused at BOOT, named - An undrawable resolved subject should be reported in its own row, not 404 the page
- A swatch and a contrast badge should take their values from the row too
- A subject naming an axis should draw one cell per value, each drawn IN it
- An axis with no subject to apply it to should be refused at BOOT
$record.*substitutes into a NESTED prop leaf, at parity with$param- A record-supplied style value cannot smuggle an external reference into a declaration
One Kit Page Serving Every Component Type
- A routed subject should draw the type the ROUTE names, so one page serves two different types
- A segment naming no drawable type should 404, rather than drawing an empty frame
- A subject named by literal should draw exactly as one named by route
- A specimen drawing two things, nothing, or an unresolvable subject should be refused at BOOT
- A refused type should be reported in place, not 404, once the PAGE has vouched for the segment
- A named
components[]template should be drawn on its own, with the config that declared it - The drawn template's own
$t:keys should resolve, leaving the page around it alone
Last updated September 23, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.