Architecture
Runtime ownership, skin layering, customization paths, and registry derivation.
Runtime and source ownership
The host application owns model calls, transport, streaming, persistence, branching, and tool execution. Control UI owns the installed component behavior, markup, local UI state, and stable anatomy. Provider-specific usage examples compose native runtime messages directly at that boundary without introducing a normalized Control UI message model.
Interactive previews are host-app integration surfaces, so they can mount a provider's official client and a server-side mock runtime. They render the provider's native messages directly; they do not add a Control UI message format or convert one provider's shape into another. Provider code never enters installable components, blocks, hooks, or utilities.
import { ChatMessage, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageHeader, ChatMessageRow,} from "@/components/control-ui/chat-message";export function AssistantMessage({ children }: { children: ReactNode }) { return ( <ChatMessage from="assistant"> <ChatMessageRow> <ChatMessageAvatar>AI</ChatMessageAvatar> <ChatMessageBody> <ChatMessageHeader>Assistant</ChatMessageHeader> <ChatMessageContent>{children}</ChatMessageContent> </ChatMessageBody> </ChatMessageRow> </ChatMessage> );}Skins over one component tree
Core renders without a skin and owns a complete neutral token baseline. Optional example skins own three files over one component source. Its theme.css explicitly resolves the complete token
contract for light and dark modes; it never inherits another skin's values. Component recipes declare registered,
typed custom-property knobs for every visual decision. Scoped skin.css re-values those knobs and owns CSS-only work:
pseudo-elements, keyframes, native chrome, relational selectors, and uniform semantic families. skin.config.tsx
contains only typed design-system behavior choices and optional adornments. Resolution order is component recipe, skin
knob values, CSS-only skin rules, then caller className.
- skin.config.tsxtyped slots · DS choices · adornments
- skin.csspseudo-elements · keyframes · descendant families
- theme.csstoken values scoped by data-skin
{ id: "refined" }. Every installed pack supplies this file; no provider or wrapper is required.The knob cascade
The whole styling system rests on four CSS rules, each verifiable in the shipped files.
@layer components behind :where(), so any
selector you write anywhere outranks the library's paint.@property { inherits: true }, and each family declares its defaults only
at its root parts. Re-value a knob on any ancestor — the skin root, a data-skin boundary, a free data-*
attribute, an inline style — and every part beneath inherits it.@layer components, any unlayered stylesheet wins against them even at
zero specificity. That is the deliberate exit for overrides — and it means a broad unlayered reset in your app
silently repaints Control UI parts too. Keep global resets in a layer.theme.css itself stays unlayered on purpose: its @theme inline block aliases every token to itself
(--radius-sm: var(--radius-sm)) so Tailwind utilities read the live cascade value, and Tailwind compiles those
aliases into @layer theme on :root. The token declarations must outrank that layer, and unlayered beats every
layer; inside @layer theme the alias would read itself and every utility would resolve to nothing.--nest-radius: toolbars, panels, code actions,
and popup bars set it, and the button and field radius knobs default to var(--nest-radius, …). A skin that
re-values --cui-button-radius or --cui-field-radius keeps that wrapper, or its buttons stop following the
container.The compiled styles set the browser floor: registered custom properties and relative color syntax — 274
oklch(from …) declarations across the recipes — require Safari 16.4, Chrome 119, and Firefox 128. One rich-tooltip
highlight uses currentColor inside relative color, which resolves only from Chrome 131, Safari 18, and Firefox 133;
below that the highlight declaration drops and the tooltip renders without it.
Stable anatomy without runtime metadata
Every public part emits three attributes, and only one of them is the selector key. data-control-family names the
family whose knobs paint the part — key on it. data-slot names the part inside its component. data-control-ui
names the component itself, for devtools, adornments and root extensions. Families that several components share add
data-<family>-kind to say which member a rule means, and data-<family>-part to name the part's role in the family.
data-skin belongs on the skin boundary, including portal positioners. It is not repeated on every component.
/* One exact part in one skin. */[data-skin="rig"] :where([data-slot="root"][data-control-family="chat-composer"]) { ... }/* One member of a shared family: toasts, not every popup. */[data-skin="rig"] :where([data-slot="root"][data-popup-kind="toast"][data-control-family="popup"]) { ... }/* Every interactive Control UI root, without touching host application controls. */[data-skin="rig"] [data-control="true"][data-control-family] { ... }The generated contract is the equivalent of a defineAnatomy catalog, but it is built from the component source
instead of becoming a browser-side object. /r/contract/index.json names every paint family; each
/r/contract/<family>.json lists that family's scopes, parts, states, adornments, and registry ownership, so an agent
restyling one family reads one file instead of the whole catalog. Item API responses separate ownScopes from
installedScopes, so an agent sees both the requested component and the anatomy brought by its dependency closure.
Keep the active skin sparse
SkinProvider accepts a consumer-owned config containing behavior choices and optional adornments. Without a provider, components use their own defaults. Each provider scopes its subtree, including portals. Component rendering
performs no runtime class lookup: recipes read registered custom properties directly from CSS. Visual breadth therefore
adds no active JavaScript, and unused component recipes remain outside an install unless the component manifest declares
them.
Bundle tests cap every shipped config at 4 kB gzip. Refined remains nearly empty; richer packs pay only for their behavior and adornment config, not once per rendered component.
Alternatives, variants, and versions
Alternatives are distinct implementations of a component. Choose the implementation that fits your project, install its registry item, and use its import. They can share a base API while accepting different data or exposing additional options. AudioVisualizer offers waveform bars and a line envelope for amplitude history, plus frequency bars for a spectrum. Centralize the import when your application should use one implementation throughout.
Variants are options of the same installed component, selected by props at each call site. They can coexist in
one application. DynamicNotification uses variant="surface", variant="glass", or variant="liquid", with optional
parts for its WebGL backdrops. The installation stays the same.
Examples demonstrate compositions, states, or integrations. Skins apply a design language across the component tree. Versions describe changes over time, such as releases and API migrations.
On component pages, the selected alternative or variant controls the preview, example code, and usage. Alternatives also select the installation and source. The URL keeps the selection so you can reload or share the same choice.
Choose the smallest customization surface
Start at the top and stop at the first rung that expresses the change. Each step downward owns more behavior and is harder to undo.
- 1Tokentheme.cssChange a named value
- 2VariantpropTwo values coexist in one app
- 3DS choiceControlUiSkinOne decision for the design system
- 4Slotskin.configReact to existing variants
- 5Pack CSSskin.cssPseudo-elements, keyframes, families
- 6Global utilityeffects.cssReusable token-driven effect
- 7Extensionoptional itemInstallable behavior or anchored effect
- 8Edit sourceowned fileRestructure the installed anatomy
Typed vocabularies stay closed. variant, tone, and every other union name what the library paints, not what one brand
needs. A look the vocabulary does not name is stamped at the call site as a free data-* attribute — every part forwards
unknown props to the DOM — and painted by re-valuing knobs under it, without a new union member or a forked component
source. Shipped skin packs key only on emitted anatomy, so the free attribute belongs in the application's own CSS, which
already outranks both the zero-specificity recipe and the skin.
/* Application CSS. The call site renders <Button data-campaign="launch" />. */[data-slot="root"][data-control-family="button"][data-campaign="launch"] { --cui-button-background: var(--brand-launch); --cui-button-hover-background: var(--brand-launch-hover);}Registry source of truth
The docs catalog and real source imports define file ownership, dependencies, and each transitive install closure. Source manifests, public payloads, previews, API metadata, and agent documentation are generated views; validation rejects drift between them.
Component visuals flow through registered CSS knob contracts, while skin config retains only behavior and adornments. The
generated contract publishes every registered --cui-* knob with its syntax and recipe default. Paint knobs carry the
-background suffix, the surface archetype tokens (--control-rim, --hover-fill, --active-fill) sit beside them, and
control sizing lives in the recipes as --cui-button-height and its padding, font-size and field twins.
This pre-release registry has no migration layer: a breaking change reinstalls core, the affected components or blocks,
and the selected skin together.