Generating a Widget Bundle
Author a complete WidgetBundle: a UEM tree (
), a JSON Schema describing the widget's input contract, and the
that registers the bundle.
When to Use This Skill
Use when the user asks for a widget, mosaic, fragment, or card-style rich UI surface. Do not use this skill for custom-LWC renderers or for
files inside a Custom Lightning Type bundle — those belong to
platform-custom-lightning-type-generate
.
Inputs
- (required) — identifier; becomes the directory name under .
- A shape — what data the widget renders. The widget cannot be generated without it. The shape arrives one of two ways, in priority order:
- — for an existing Apex-backed Lightning Type. The FQN takes one of two forms: outer-class () where the outer class is the payload, or inner-class (
<namespace>__<ClassName>$<InnerClass>
) where the named inner class is the payload. Passed in by the platform-lightning-type-widget-coordinate
orchestrator. When present, derive per references/schema-from-lightning-type.md
.
- Extracted from the user's prompt — when no is passed, infer the shape directly from what the user wrote: a pasted JSON payload, an enumerated field list ("id as string, total as number"), or descriptive prose. The output is the same ordered list of either way.
If neither source yields a shape, STOP and ask the user before proceeding.
Output
Three files in
<pkgDir>/uiWidgets/<widgetName>/
:
| File | Content |
|---|
| Widget envelope — { "type": "lightning__agentforceWidget", "contentBody": { "widgetBody": { UEM tree rooted at tile/widget } } }
|
| JSON Schema — root has + wrapper carrying lightning:type: "lightning__objectType"
and the field |
<widgetName>.uiwidget-meta.xml
| element with , , and <widgetType>JSON</widgetType>
|
See
references/widget-bundle-layout.md
for the
resolution procedure and the exact
<widgetName>.uiwidget-meta.xml
shape.
Composition
A widget body is a UEM tree of blocks nested under
. The root node is
. Every node — root and non-root — has the same shape: no
key; just
, optional
, optional
, and optional
. Block shape:
ts
interface Block {
definition: string // {namespace}/{blockName} — root is "tile/widget"
attributes?: Record<string, any>
meta?: { // see references/widget-meta-directives.md
forEach?: string
forItem?: string
if?: string
}
children?: Block[]
}
The first child of
SHOULD be a single
(or a single
). All widget content typically goes inside that first child for predictable vertical structure across surfaces.
Available Metadata Actions
discoverUiComponents
Purpose: Discover the palette of blocks available for composition.
Required parameters: actionName: "discoverUiComponents"
,
,
parameters.pageType: "FRAGMENT"
. Optional:
to filter by name/description.
Returns: list of
{ definition, description, label, attributes? }
.
getUiComponentSchemas
Purpose: Fetch JSON schemas (property types, required vs optional, validation) for selected blocks.
Required parameters: actionName: "getUiComponentSchemas"
,
,
parameters.pageType: "FRAGMENT"
,
parameters.componentDefinitions: ["namespace/definition", ...]
. Optional:
(default
).
Returns: — success entries carry the JSON schema, failure entries carry an error message. Partial failures are supported.
Never pass
to
— it is a fixed wrapper, not a queryable component.
Attribute Binding
- Bind a block property to runtime data with . MUST match a property name in .
- Inside a , reference the loop variable instead — e.g. . See
references/widget-meta-directives.md
.
Layout Best Practices
These conventions cover widget structure — how blocks are grouped and stacked.
| Primitive | Purpose | When to use |
|---|
| Vertical stack of children | Root wrapper, and any group of blocks that should stack |
| Horizontal stack of children | Two or more blocks that belong on the same line |
| Visually-boxed group | A bounded section that should read as one unit |
| Whitespace between blocks | When extra space is needed between content groups |
- Sectioning: Separate major content groups with a fresh -bounded section. Do not nest cards inside cards.
- Nesting: Prefer flat layouts. Only nest a inside a (or vice versa) when the visual orientation actually changes for that subgroup.
- Authoritative palette: the table above lists typical layout primitives. Always confirm a block exists by inspecting output — do not assume a block name from this table without seeing it in the discovery response.
Styling Best Practices
Widgets express intent, not pixels. Each surface provides a default look and feel; brand/theme overrides apply automatically.
- Style semantically. Use , , and other enum-typed attributes (, , , ). Do not pin literal colors or pixel values.
- One primary action per visible group. At most one with . Use , , or for additional actions.
- One per widget. Use / for sub-section headings, for prose, for helper text.
- Use semantic state variants on state-bearing blocks (, , , ).
- Accept schema defaults for , , unless there is a specific reason to override.
- Don't pin , , unless a content constraint requires it. For long text, use .
- Use the Lucide icon set. Pass the Lucide name (, ); other icon libraries are not supported.
Workflow
-
Resolve the widget spec — an ordered list of
. Source depends on which input was provided (see
Inputs):
- If was passed by the orchestrator → derive per
references/schema-from-lightning-type.md
.
- Otherwise → infer the list directly from the user prompt (pasted JSON payload, enumerated field list, or descriptive prose).
-
Discover blocks (REQUIRED — do NOT skip). Call the
metadata action via
. Use property types from the widget spec to seed
(text →
, number →
).
If returns , an error, or an empty list, STOP and surface the error verbatim — do not improvise block names from memory, prior runs, or training data. Re-run discover with a different only if the failure is search-query-specific.
-
Select blocks. Choose one block per widget-spec property, plus structural primitives from Layout Best Practices.
-
Get block schemas (REQUIRED — do NOT skip). Call the
metadata action via
for the selected blocks. Review property metadata.
If returns all-failure or empty, STOP and surface the error — do not improvise from existing widgets in the project.
-
Build the UEM tree (example reads REQUIRED — do NOT skip). First, identify which patterns match the widget spec and read each matching example file from this skill's own
directory (
):
| Pattern in the spec | Example to read |
|---|
| Single object (no iteration) | <skill-root>/examples/single-object.json
|
| Any list iteration (root-level array, nested list, or list embedded in a single-object widget) | <skill-root>/examples/list-with-foreach.json
|
| Conditional rendering ( bound to a boolean) | <skill-root>/examples/conditional.json
|
A spec may match multiple patterns (e.g. a list of items where some items render conditionally reads both
and
).
Read every matching example, and only those — do not skip the read because the pattern feels familiar.
Then:
- Map each widget-spec property to a block property; preserve spec order.
- Decide root iteration: single object → properties directly under root . Collection → wrap repeating block in /. See
references/widget-meta-directives.md
.
- Bind values with (or inside ).
- For conditional blocks, add on — only when the schema has a matching property.
-
Author . Build the JSON Schema from the widget spec. Fields live one level deep under an
wrapper:
json
{
"title": "<Widget Display Name>",
"description": "<one line about what the widget shows>",
"type": "object",
"properties": {
"attributes": {
"lightning:type": "lightning__objectType",
"properties": {
"<propertyName>": {
"title": "<label>",
"description": "<short description>",
"lightning:type": "<lightning__textType | lightning__numberType | ...>"
}
}
}
}
}
Required root keys: ,
,
(with
lightning:type: "lightning__objectType"
and a nested
map). See
references/schema-from-lightning-type.md
for full primitive type guidance.
-
Author <widgetName>.uiwidget-meta.xml
. See
references/widget-bundle-layout.md
for the exact shape.
-
Resolve and write the bundle. Follow the procedure in
references/widget-bundle-layout.md
(
). A widget bundle is a
three-file set — all three files must be written in the same step; a bundle with fewer than three files is incomplete and will not deploy.
text
<pkgDir>/uiWidgets/<widgetName>/<widgetName>.json # widget envelope — UEM tree (primary artifact)
<pkgDir>/uiWidgets/<widgetName>/schema.json # attribute contract for the envelope
<pkgDir>/uiWidgets/<widgetName>/<widgetName>.uiwidget-meta.xml # UiWidgetBundle registration
Each file has a distinct role:
- — the widget envelope with the UEM tree. This is the primary artifact; is its companion contract, not a substitute.
- — the JSON Schema for the attributes referenced by bindings in the envelope.
<widgetName>.uiwidget-meta.xml
— the element that registers the bundle for source tracking and deployment.
Write all three before proceeding to self-validation.
-
Self-validate. Before reporting, confirm each check below and report each result individually (
or
). Do
not summarize as a single "all passed" line — list every check so a reviewer can spot a silent skip.
- —
<pkgDir>/uiWidgets/<widgetName>/schema.json
parses as JSON.
- — root has (string), , and (object) — where carries
lightning:type: "lightning__objectType"
and a nested map. No unevaluatedProperties: false
.
- — every leaf under
properties.attributes.properties
carries a . Singular nested inner-class fields appear as ; the nested shape is not redeclared.
- — every (or
{!$attrs.<outerField>.<innerField>}
for nested objects) in resolves to a property under properties.attributes.properties
, and every resolves to a loop variable defined upstream.
- — root has
type: "lightning__agentforceWidget"
and a object whose carries the UEM tree rooted at . No node in the tree — root or non-root — carries a key.
- —
<widgetName>.uiwidget-meta.xml
parses as well-formed XML.
- —
<widgetName>.uiwidget-meta.xml
has root and contains (non-empty), (non-empty), and <widgetType>JSON</widgetType>
.
- — all three files exist at the resolved
<pkgDir>/uiWidgets/<widgetName>/
path.
Rules / Constraints
| Constraint | Rationale |
|---|
| Block definitions follow and must match output | Runtime resolves blocks by exact definition string |
| Never pass to | It is a fixed wrapper, not a queryable component |
| Always supply (with required keys) when calling | Missing parameters cause hard failure, not partial result |
| Every in the body resolves to a property in the widget | No invented fields |
| No , backticks, , brace expansion , or / in any Bash tool call | Vibes' safe-shell filter forces manual approval on these patterns even in Bypass mode. Emit separate commands () or print each value with its own command and reason about the output — do not capture into a shell variable |
Gotchas
| Issue | Resolution |
|---|
| returns a partial-failure entry | Pick a different block from ; do not silently continue without a schema |
Body references but is not under properties.attributes.properties
| Add to properties.attributes.properties
OR remove the body reference |
Output written outside <pkgDir>/uiWidgets/<widgetName>/
| = <packageDirectories[].path>/main/default
(see references/widget-bundle-layout.md
). Dropping the segment is the common cause of widgets landing at instead of force-app/main/default/uiWidgets/...
|
| bound to a non-boolean | Use only when the schema has a property |
Reference File Index
| File | When to read |
|---|
references/widget-meta-directives.md
| For / (iteration) and (conditional rendering), including nested loops |
references/schema-from-lightning-type.md
| When is provided; how to derive the widget from an Apex-backed Lightning Type |
references/widget-bundle-layout.md
| Folder layout, shape, resolution rules |
examples/single-object.json
| Single-object pattern (root binding via , no iteration) |
examples/list-with-foreach.json
| Any list-iteration case — root-level collections, nested lists, and lists embedded inside a single-object widget (e.g. iterating a inside an outer Apex payload) |
examples/conditional.json
| Conditional pattern ( on , including + together) |