Skip to main content
@widgentic/designer ships five designers — widget, theme, schema, action and settings — each as a function taking a container and each as an opt-in custom element. They run in the browser, depend only on @widgentic/core, and perform no network I/O: every edit re-runs widgentic’s validators and re-renders a live preview through the real pipeline; persistence is yours.

The widget designer

createDesigner(container, options?) accepts: The handle is { getDraft, getDiagnostics, loadWidget, checkWidget, loadTheme, setReadOnly, subscribe, dispose }. getDraft() returns a copy of the current draft; getDiagnostics() returns its diagnostics and checkWidget(definition) checks a definition as a load would, without touching the draft, both resolved against the schemas and actions you supplied; loadWidget(definition) and loadTheme(theme) validate first and return { ok: true } or { ok: false, errors } without touching the draft on failure; loadTheme(tokens) layers its tokens over the preview theme the person chose, which stays selected with its styles, so loadTheme({}) returns to it; loadWidget keeps the preview theme and refuses a kind Save would refuse only when the load changes the kind; subscribe(listener) calls listener(draft, diagnostics) on every change and returns an unsubscribe function; dispose() removes the UI. Read-only mode — readOnly: true or setReadOnly(true) — leaves editing surfaces visible but inert while the preview, its theme selector and Export keep working: it restricts editing, not looking.

Theme, schema, action and settings designers

  • createThemeDesigner edits a named entry { name, label?, description?, tokens, styles? }, its shared styles in the widget designer’s own Styles panel. Its widgets option adds custom widget definitions to the preview-kind selector beside the built-ins, so a theme is judged against the widgets it will dress; invalid definitions are skipped. initialTheme opens an existing entry.
  • createSchemaDesigner edits a shared schema entry { name, label?, description?, schema }; initialSchema opens one.
  • createActionDesigner edits a stored action { name, label?, description?, definition }; initialAction opens one. schemas offers shared schemas as copy-from sources for the input and output schemas, and secretNames offers names for header and query values. A Test control appears only when you pass testCall(definition, args), which must run through your server’s execution path — the library performs no network I/O itself.
  • createSettingsDesigner edits the principal’s settings record. Its Default themes subsection offers, per mode (light hosts, dark hosts), the built-in and every entry you pass in themes; getSettings() returns { defaultThemes: { light?, dark? } }, built-in choices omitted, with any section it does not edit passed through untouched. Save it with PUT /settings on the authoring surface. A name you did not pass in themes shows as unavailable rather than disappearing. Its Resource domains subsection lists the domains the principal’s frames may load images from directly. Each entry is checked as it is typed by the same rule the store applies (parseResourceDomain from @widgentic/core). A valid entry is recorded in its canonical https:// form; an invalid one is kept as typed and marked, so the save’s INVALID_DOMAIN names it. Adding stops at RESOURCE_DOMAIN_LIMIT. getSettings() carries resourceDomains once the record had it or the person edited the list, so a save clears an emptied list.
The preview renders widgets in your page’s DOM, not in a frame. Its styles stay inside it: the widget’s and the theme’s rules are scoped to the preview. Your page’s element rules are another matter. A rule like header { border-bottom: … } would dress a widget’s <header> that no MCP Apps host dresses, so fence them out of the preview:
:where() keeps the rule’s weight unchanged. Rules on your own classes need no fence. All five accept appearance, chrome and readOnly. The widget designer only selects among the themes, schemas and actions you pass; authoring each belongs to its own designer, so a host ships only what it embeds.

Custom elements

Registration happens only through the explicit calls — importing the module has no registry side effects:
Each registrar takes an optional tag name and does nothing if that tag is already defined. The elements wrap their factory and re-emit its subscription as bubbling widgentic-change CustomEvents whose detail carries { draft, diagnostics }, { theme }, { schema }, { action } or { settings }. Two attributes are read when the element connects: appearance (light or dark) and chrome (the token map as JSON). The wrapped handle is available as the element’s designer property. Without a bundler, load the single-file bundle and use the elements directly:

Theming the chrome

chrome is available from @widgentic/designer 0.2.0. From 0.3.0 the built-in defaults are the widgentic palette, and CHROME_DEFAULTS and chromeCss are exported.
The designers paint their own UI — panels, inputs, buttons, tags, menus — through 28 --wgd-* custom properties, the chrome tokens (exported as CHROME_TOKENS). The widget preview inside them is not chrome: it follows the selected theme’s --wg-* tokens from @widgentic/core/theming and is unaffected by chrome. By default the designers wear the widgentic palette, light and dark — the same colours the product uses, so an embedded designer looks like widgentic without you configuring anything. Every colour sits on the logo mark’s hue, and each one is contrast-checked in both schemes: text pairs at WCAG AA (4.5:1), and the borders that identify an input, a focused tag or a banner at 3:1. Typography is deliberately not branded — font, font-mono and the three sizes stay system values, because a library must never default to a webfont it cannot serve. appearance picks which scheme’s defaults the chrome follows. chrome layers your values on top of them, applied inline on the designer root, so they win over the light and dark blocks:
The elements take the same map as JSON:

Match your page to the designers

CHROME_DEFAULTS is not a description of the defaults — it is them; the injected stylesheet is generated from it. Paint your own surfaces from the same object and the two can never drift — and chromeReferences() is the return trip: the full chrome map of var() references under the same prefix, so the designers follow whatever your page declares. Together they are the full-takeover recipe:
A var() reference to a property your page never defines is invalid at computed-value time — it does not fall back to the built-in defaults. Pair chromeReferences() with chromeCss under the same prefix, and use a partial chrome map instead when you only want to change a few tokens (unmapped tokens keep the product defaults).
Painting the page directly from the exported object also works:
chromeCss(palette, options?) takes prefix (default --wgd), selector (default :root), darkMediaSelector and an optional darkSelector for a toggle you control. It returns a string; nothing is injected for you.

Use your own palette instead

There is one default palette and no second one to fall back to — a host that wants a different look passes its own values, and chromeCss will render them for the surrounding page just the same:
Contrast is then yours to own: the thresholds the built-in palette is held to are in chrome-contrast.test.ts if you want the same gate. Rules: values are CSS values; CSS-wide keywords (inherit, initial, unset, revert) are ignored — on a custom property they act on the token itself, not on what reads it — so to follow your typeface pass its variable with a fallback stack (font: "var(--app-font, system-ui, sans-serif)"). Unknown tokens, non-string values and unparseable attribute JSON are ignored, never thrown. With no chrome, you get the widgentic palette.

Helpers

The entry also exports the pieces the designers are built from — importWidgetJson, exportWidgetJson, exportThemeJson, toTypeScriptModule, and checkThemeEntry, checkSchemaEntry, checkActionEntry, which validate an untrusted entry and return error strings instead of throwing. The full list is in the designer export index.