COMPONENTS FEATURE
Primitives & tokens
Buttons, badges and inputs rendered on the server from the platform-ui primitive registry, drawn entirely by the active skin's design tokens.
Primitives & tokens
Primitives are the atomic building blocks of Semitexa UI. They are semantic HTML elements styled entirely through a ui="<id>" attribute plus modifiers — never through bespoke class names. The CSS that an attribute resolves to is the stable contract; the Twig helper is optional convenience on top of it.
The six v1 primitives
v1 ships six primitives, all semantic HTML:
| Primitive | Element | Key modifiers |
|---|---|---|
button |
<button> / <a> |
ui-variant (solid·soft·ghost), ui-tone (neutral·brand·success·warning·danger), ui-size (sm·md·lg) |
input |
<input> |
ui-size, ui-state (default·invalid) |
label |
<label> |
ui-size |
field-shell |
wrapper | ui-state="invalid" cascades danger to descendant label / input / error-text |
surface |
panel | composes with sx-padding / sx-radius / sx-surface |
badge |
inline | ui-variant (solid·soft), ui-tone |
For honesty: textarea, select, checkbox, radio, switch and friends are deferred to v1.1+ — they are not part of the v1 primitive set. (The collaboration demo's textareas are plain <textarea ui="input"> elements borrowing the input slice, not a textarea primitive.)
Rendering a primitive
The primitive(name, props) Twig helper renders any primitive from the registry. This page's live preview is exactly this markup:
{{ primitive('button', { text: 'Primary action', tone: 'brand', variant: 'solid' }) }}{{ primitive('button', { text: 'Secondary', tone: 'brand', variant: 'soft' }) }}{{ primitive('button', { text: 'Subtle', tone: 'neutral', variant: 'ghost' }) }}{{ primitive('badge', { text: 'Stable', tone: 'success' }) }}{{ primitive('badge', { text: 'Beta', tone: 'warning' }) }}{{ primitive('input', { name: 'demo_input', placeholder: 'A token-styled input…', help: 'Same primitive, any skin.' }) }}The prop vocabulary is small and explicit. Only text (and href on a button) ever changes the rendered tag; every other prop maps to a ui-* attribute that the active skin's tokens.css resolves. error on an input automatically sets ui-state="invalid", aria-invalid="true" and an inline danger message; help renders muted help text wired through aria-describedby.
How it resolves — registry, marker, tokens
A class becomes a primitive by carrying #[AsUiPrimitive]; the BootPlatformUiRegistryListener boots the UiPrimitiveRegistry at worker start so primitives are discoverable by canonical name (platform.button) or short alias (button). The two are interchangeable: primitive('platform.button', …) ≡ primitive('button', …).
The rendered output carries a stable root marker for the frontend runtime to scan:
<button ui="button" data-ui-primitive="platform.button" type="button" ui-tone="brand">Save</button>Crucially, that markup contains no colour, no radius, no spacing literal — only the ui-* attributes. Platform-UI CSS reads every visual decision from --ui-* custom properties defined by the active skin (/assets/skins/<slug>/tokens.css), loaded last so it wins the cascade.
When to reach for a primitive
Use a primitive when you need an atomic control — a button, an input, a badge. When you need to arrange primitives (a labelled field, a toolbar, a card), compose them with the sx-* / ui-* grammar (see The sx-* / ui-* grammar) or build a component out of #[UiPart] / #[UiSlot] (see Live field validation).
How it ties to the philosophy
Primitives are the Token-driven UI pillar at the atomic level. Because the same primitive renders in moss here, terracotta on framework.semitexa.test, and any future skin with zero per-site overrides, visual identity stays a data concern (the skin) rather than a code concern (the component). The palette is guaranteed consistent across light and dark mode because both modes are emitted by the same 41-token contract, and components stay portable because they never learn the brand — they only ever speak --ui-*.
How it works
A class becomes a primitive via #[AsUiPrimitive]; the registry boots at worker start so primitives resolve by canonical name (platform.button) or short alias (button). The rendered markup carries a stable data-ui-primitive marker and nothing but ui-* attributes — no colour, radius or spacing literal. Every visual decision resolves from --ui-* tokens defined by the active (moss) skin, loaded last so it wins the cascade. Six primitives ship in v1 (button, input, label, field-shell, surface, badge); textarea/select/checkbox are deferred to v1.1+.
Why it matters
One token surface means visual identity is a data concern (the skin), not a code concern. The same primitive renders in moss here and terracotta on framework.semitexa.test with zero per-site override; the palette stays consistent across light and dark because both modes are emitted by the same 41-token contract. Components stay portable because they never learn the brand — they only ever speak --ui-*.