This document is a Draft companion specification to the Formspec v1.0 Core Specification. It defines the Formspec Component Document format — a sidecar JSON document that describes a parallel presentation tree of UI components bound to a Formspec Definition’s items.
Status: Draft Companion Specification Version: 1.0.0 Date: 2025-01-14 Depends on: Formspec Core Specification v1.0 (spec.md), Formspec Theme Specification v1.0 (theme-spec.md), FEL Normative Grammar v1.0 (fel-grammar.md)
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
JSON syntax and data types are as defined in [RFC 8259]. JSON Pointer syntax is as defined in [RFC 6901]. URI syntax is as defined in [RFC 3986].
JSON examples use // comments for annotation; comments
are not valid JSON. Property names in monospace (component)
refer to JSON keys. Section references (§N) refer to this document
unless prefixed with “core” (e.g., “core §4.2.5”) or “theme” (e.g.,
“theme §3”).
$formspecComponent,
version, targetDefinition, and
tree.schemas/component.schema.json;
generated schema references are the canonical structural contract.
The Formspec Core Specification defines what data to collect (Items, core §4.2) and how it behaves (Binds, Shapes). The Formspec Theme Specification (theme-spec.md) defines Tier 2 presentation: a selector cascade, design tokens, widget configuration, and page layout that control how a Definition is rendered.
This specification defines Tier 3 of the Formspec presentation model: a Component Document that describes a complete, parallel presentation tree of UI components. Where Tier 2 maps items to widgets one-to-one via selectors, Tier 3 builds an explicit tree of layout containers, input controls, and display elements, with each input component slot-bound to a Definition item by key.
A Component Document:
when
property).Multiple Component Documents MAY target the same Definition. This enables platform-specific presentations (web wizard, mobile single-page, print layout) without modifying the Definition or its behavioral rules.
The Formspec architecture defines a three-tier presentation model:
| Tier | Concern | Defined In |
|---|---|---|
| 1. Structure hints | Advisory widget hints on Items | Core §4.2.5 (presentation) |
| 2. Theme | Selector cascade, tokens, layout grid | Theme Spec (theme-spec.md) |
| 3. Component | Explicit component tree with slot binding | This specification |
Tier 3 is the most expressive layer. When a Component Document is applied, it takes precedence over both Tier 2 themes and Tier 1 inline hints for layout and widget selection. However, the Definition’s behavioral rules (required, relevant, readonly, constraint, calculate) always govern data semantics — Tier 3 cannot override them.
Tier 3 MAY coexist with a Tier 2 theme. When both are present:
$token. references within
the Component Document (§10.3).FEL expressions (fel-grammar.md) are used in the when
property for conditional rendering (§8). FEL is NOT used for computed
props, data transformation, or any purpose other than boolean visibility
conditions and display text interpolation.
This specification defines two conformance levels:
| Level | Components | Requirement |
|---|---|---|
| Core Conformant | 17 Core components (§5) | MUST support all 17 Core components. MUST apply fallback rules (§6.17) when encountering Progressive components. |
| Complete Conformant | All 33 components (§5 + §6) | MUST support all 17 Core components and all 16 Progressive components. |
A processor that claims Core conformance MUST, upon encountering a Progressive component, substitute the specified Core fallback (§6.17) and SHOULD emit an informative warning.
A processor that claims Complete conformance MUST render all 33 built-in components natively.
Both levels MUST support the custom component mechanism (§7).
| Term | Definition |
|---|---|
| Definition | A Formspec Definition document (core spec §4). |
| Component Document | A Formspec Component document conforming to this specification. |
| Component | A node in the component tree. Each component has a type, optional binding, and optional children. |
| Tier 1 hints | The formPresentation and presentation
properties defined in core spec §4.1.1 and §4.2.5. |
| Tier 2 theme | A Formspec Theme Document conforming to theme-spec.md. |
| Tier 3 component | A Component Document conforming to this specification. |
| Renderer | Software that presents a Definition to end users using a Component Document. |
| Token | A named design value (color, spacing, typography) defined in the
tokens map. |
| Slot binding | The association between a component and a Definition item via the
bind property. |
| Core component | One of the 17 components that all conforming processors MUST support. |
| Progressive component | One of the 16 additional components that Complete processors MUST support, with defined fallbacks for Core processors. |
| Custom component | A reusable component template defined in the components
registry. |
A Formspec Component Document is a JSON object. Conforming implementations MUST recognize the following top-level properties and MUST reject any Component Document that omits a REQUIRED property.
{
"$formspecComponent": "1.0",
"url": "https://agency.gov/forms/budget/components/wizard",
"version": "1.0.0",
"targetDefinition": {
"url": "https://agency.gov/forms/budget",
"compatibleVersions": ">=1.0.0 <2.0.0"
},
"breakpoints": {
"sm": 576,
"md": 768,
"lg": 1024
},
"tokens": {},
"components": {},
"tree": {}
}| Pointer | Field | Type | Required | Notes | Description |
|---|---|---|---|---|---|
#/properties/$formspecComponent |
$formspecComponent |
string |
yes | const: “1.0”; critical |
Component specification version. MUST be ‘1.0’. |
#/properties/breakpoints |
breakpoints |
$ref |
no | $ref: #/$defs/Breakpoints |
Named viewport breakpoints for responsive prop overrides. Keys are breakpoint names; values are minimum viewport widths in pixels. Mobile-first cascade: base props apply to all widths, then overrides merge in ascending order. |
#/properties/components |
components |
object |
no | — | Registry of custom component templates. Keys are PascalCase names (MUST NOT collide with built-in names). Each template has params and a tree that is instantiated with {param} interpolation. |
#/properties/description |
description |
string |
no | — | Human-readable description. |
#/properties/name |
name |
string |
no | — | Machine-friendly short identifier. |
#/properties/targetDefinition |
targetDefinition |
$ref |
yes | $ref: #/$defs/TargetDefinition;
critical |
Binding to the target Formspec Definition and optional compatibility range. |
#/properties/title |
title |
string |
no | — | Human-readable name. |
#/properties/tokens |
tokens |
$ref |
no | $ref: #/$defs/Tokens |
Flat key-value map of design tokens. Referenced in style objects and token-able props via $token.key syntax. Tier 3 tokens override Tier 2 theme tokens of the same key. |
#/properties/tree |
tree |
$ref |
yes | $ref: #/$defs/AnyComponent; critical |
Root component node of the presentation tree. MUST be a single component object (wrap multiple children in Stack or Page). |
#/properties/url |
url |
string |
no | — | Canonical URI identifier for this Component Document. |
#/properties/version |
version |
string |
yes | critical | Version of this Component Document. |
The generated table above is the canonical structural contract for Component Document top-level properties.
Processors MUST ignore unrecognized top-level properties whose keys
begin with x-. Processors MUST reject unrecognized
top-level properties that do NOT begin with x-.
The targetDefinition object binds this Component
Document to a specific Definition.
| Property | Type | Cardinality | Description |
|---|---|---|---|
url |
string (URI) | 1..1 (REQUIRED) | Canonical URL of the target Definition (url property
from the Definition). |
compatibleVersions |
string | 0..1 (OPTIONAL) | Semver range expression (e.g., ">=1.0.0 <2.0.0")
describing which Definition versions this Component Document supports.
When absent, the document is assumed compatible with any version. |
When compatibleVersions is present, a processor SHOULD
verify that the Definition’s version satisfies the range
before applying the component tree. A processor MUST NOT fail if the
range is unsatisfied; it SHOULD warn and MAY fall back to Tier 2/Tier 1
rendering.
The binding mechanism is identical to the Theme Specification’s
targetDefinition (theme-spec §2.2). A Component Document
and a Theme Document MAY target the same Definition simultaneously.
Formspec Component Documents SHOULD use the file extension
.formspec-component.json.
When served over HTTP, the content type SHOULD be
application/json. Processors MAY recognize the custom media
type application/formspec-component+json when
registered.
The file extension convention enables tooling to distinguish
Component Documents from Definition documents
(.formspec.json) and Theme Documents
(.formspec-theme.json) by filename alone.
The following is the smallest valid Component Document:
{
"$formspecComponent": "1.0",
"version": "1.0.0",
"targetDefinition": {
"url": "https://example.com/form"
},
"tree": {
"component": "Stack",
"children": [
{ "component": "TextInput", "bind": "name" }
]
}
}This document:
$formspecComponent version "1.0"
(REQUIRED).version for the document itself
(REQUIRED).targetDefinition
(REQUIRED).tree with a single Stack
containing one TextInput bound to the item key
"name" (REQUIRED).All other top-level properties (url, name,
title, description, breakpoints,
tokens, components) are OPTIONAL and default
to empty/absent.
A component tree is a hierarchical structure of component objects. Each component object describes a single UI element — a layout container, an input control, a display element, or a structural grouping.
Every component object is a JSON object. The following base properties are recognized on all component objects:
| Property | Type | Cardinality | Description |
|---|---|---|---|
component |
string | 1..1 (REQUIRED) | The component type name. MUST be a built-in component name (§5, §6)
or a key in the components registry (§7). |
id |
string | 0..1 (OPTIONAL) | Unique identifier within the component tree document. See below. |
bind |
string | 0..1 (varies) | Item key from the Definition. See §4 for rules per component category. |
when |
string (FEL) | 0..1 (OPTIONAL) | FEL boolean expression for conditional rendering. See §8. |
responsive |
object | 0..1 (OPTIONAL) | Breakpoint-keyed prop overrides. See §9. |
style |
object | 0..1 (OPTIONAL) | Flat style map. Values MAY contain $token.path
references. See §10.2. |
cssClass |
string | array of strings | 0..1 (OPTIONAL) | CSS class name(s) that web renderers SHOULD apply to the component’s
root element. Additive to renderer-generated classes. Non-web renderers
MAY ignore. Values MAY contain $token. references. |
accessibility |
AccessibilityBlock | 0..1 (OPTIONAL) | Accessibility overrides applied to the component’s root element. See §3.5. |
children |
array | 0..1 (varies) | Array of child component objects. Only components that accept children (§3.4) MAY include this property. |
id (Optional)Components MAY include an id property — a unique string
identifier within the component tree document. The id MUST
match the pattern ^[a-zA-Z][a-zA-Z0-9_\-]*$ (letters,
digits, underscores, hyphens; must start with a letter).
When present, id MUST be unique across the entire
component tree document. The id enables:
$component.<id>.<prop> keys in Locale Documents
(locale spec §3.1.8).Processors MUST validate id uniqueness when both
id values and the component tree are available:
id collision MUST
produce an error.id collision SHOULD produce a
warning and the processor MUST bind only the
first occurrence in document order (matching the
editable binding uniqueness pattern in §4.3).When a component node with id appears inside a repeat
template (as a child of a DataTable bound to a repeatable group per
§6.13, or an Accordion per §6.3), the id identifies the
template node, not individual rendered instances. All
rendered instances of the template share the same id. This
is consistent with the template-instantiation rendering model defined in
§4.4 — the component tree document contains one subtree that the
renderer instantiates N times.
For locale addressing, this means a single locale key
$component.<id>.<prop> applies to all repeat
instances. If per-instance text is needed, the locale string value may
use FEL interpolation with @index and @count
(evaluated in each repeat instance’s binding scope).
In addition to these base properties, each component type defines its own component-specific props (documented in §5 and §6). Component-specific props are siblings of the base properties in the same JSON object.
Example of a fully-specified component object:
{
"component": "TextInput",
"bind": "projectName",
"when": "$hasProject = true",
"placeholder": "Enter project name",
"style": {
"borderColor": "$token.color.primary"
},
"responsive": {
"sm": { "placeholder": "Project" }
}
}Processors MUST ignore unrecognized properties on component objects. This enables forward-compatible extension.
The tree property MUST contain exactly
one component object. This object is the
root of the component tree.
To present multiple components at the top level, authors MUST wrap
them in a layout component (typically Stack or
Page):
// ✗ INVALID — tree cannot be an array
"tree": [
{ "component": "TextInput", "bind": "name" },
{ "component": "TextInput", "bind": "email" }
]
// ✓ VALID — single root wrapping multiple children
"tree": {
"component": "Stack",
"children": [
{ "component": "TextInput", "bind": "name" },
{ "component": "TextInput", "bind": "email" }
]
}The root component MAY be any component that accepts children (Layout or Container). A root that is a leaf component (Input or Display) is valid but yields a form with a single element.
The children array defines an ordered
list. Renderers MUST preserve the array order when rendering:
Renderers MUST NOT reorder children unless explicitly instructed by a responsive override (§9) that changes layout properties (e.g., switching a Stack’s direction), but even then the logical array order is preserved within the new layout direction.
Components are classified into four categories. Nesting rules depend on the category:
| Category | Accepts children |
Examples |
|---|---|---|
| Layout | Yes | Page, Stack, Grid, Columns, Tabs, Accordion |
| Container | Yes | Card, Collapsible, ConditionalGroup, Panel, Modal, Popover |
| Input | No | TextInput, NumberInput, Select, Toggle, … |
| Display | No | Heading, Text, Divider, Alert, Badge, … |
Rules:
Layout and Container components MAY contain any component type as children (Layout, Container, Input, or Display), unless further restricted by the specific component (e.g., Tabs children SHOULD be Page components for correct tab rendering).
Input and Display components MUST NOT have a
children property. If present, processors MUST reject the
document or ignore the children property and emit a
warning.
Spacer MUST NOT have children (it is a Layout leaf).
Nesting depth SHOULD NOT exceed 20 levels. Processors MAY reject documents exceeding this limit.
The optional accessibility property on any component
object is an AccessibilityBlock — a flat object that
lets authors override or supplement the ARIA attributes applied to the
component’s root element.
| Property | Type | Description |
|---|---|---|
role |
string | ARIA role override (e.g., "region",
"group", "status"). Replaces any
renderer-default role. |
description |
string | Accessible description text. Renderers SHOULD wire this to
aria-describedby (not aria-description, which
is not a valid ARIA attribute). |
liveRegion |
"off" | "polite" |
"assertive" |
Sets aria-live on the root element. Renderers MUST NOT
apply role="status" or any live-region semantics unless
this property is explicitly set. |
Renderers MUST apply all present AccessibilityBlock
properties to the component’s root DOM element. If a property is absent,
the renderer’s default behaviour is preserved.
The following table lists component properties that contain
human-readable text addressable by Locale Documents via
$component.<id>.<prop> keys. Only components
with an id property are addressable.
| Component | Localizable Props |
|---|---|
| Page | title, description |
| Heading | text |
| Text | text |
| Alert | text |
| Divider | label |
| Card | title, subtitle |
| Collapsible | title |
| ConditionalGroup | fallback |
| Tabs | tabLabels[N] |
| Accordion | labels[N] |
| SubmitButton | label, pendingLabel |
| DataTable | columns[N].header |
| Panel | title |
| Modal | title, triggerLabel |
| Popover | triggerLabel |
| Badge | text |
| ProgressBar | label |
| Summary | items[N].label |
| Select | placeholder |
| TextInput | placeholder, prefix,
suffix |
Array-valued properties use bracket indexing with numeric indices
(e.g., $component.mainTabs.tabLabels[0]).
Slot binding is the mechanism by which components in the presentation
tree are associated with items in the Formspec Definition. The
bind property on a component object establishes this
association.
The bind property is a string that
identifies an item in the target Definition. It accepts two forms:
key matching a
top-level item (e.g., "projectName")."applicantInfo.orgName").The bind value is NOT a JSON Pointer or FEL
expression.
// Top-level item — flat key:
{ "component": "TextInput", "bind": "projectName" }
// Nested item — dotted qualified path:
{ "component": "TextInput", "bind": "applicantInfo.orgName" }The bind value MUST be a non-empty string. The value
MUST correspond to an item key (or a dotted path resolving
to a nested item) in the target Definition. If the key does not resolve
to any item in the Definition, the processor MUST emit a warning and
SHOULD hide the component.
The meaning and requirement of bind varies by component
category:
| Category | bind |
Behavior |
|---|---|---|
| Input | REQUIRED | The component reads and writes the bound item’s value. The renderer
MUST propagate the item’s required, readOnly,
and relevant state to the input control. Validation errors
for the bound key MUST be displayed adjacent to this component. |
| Display | OPTIONAL | When present, the component displays the bound item’s current value
as read-only content. When absent, the component renders its static
text prop. |
| Layout | FORBIDDEN | Layout components MUST NOT have a bind property. If
present, processors MUST ignore it and emit a warning. |
| Container | FORBIDDEN | Container components MUST NOT have a bind property,
with the exceptions of DataTable (§6.13) and
Accordion (§6.3), which MAY bind to a repeatable
group. |
When an Input component is bound to a field item:
label.hint when available.required expression
evaluates to true.readOnly expression evaluates to
true or when the item has a calculate
Bind.relevant expression evaluates to false, the
renderer MUST hide the component. This operates independently of the
component’s when property (§8.2).At most one editable Input component MAY bind to a given item key. If two or more editable Input components bind to the same key, the processor MUST reject the document or emit a warning and bind only the first occurrence.
Multiple read-only Display components MAY bind to the same key. This is useful for showing a field’s value in a summary section while also rendering an input elsewhere.
Example:
// ✓ VALID — one input + one display for same key
{ "component": "NumberInput", "bind": "totalBudget" }
// ... elsewhere in the tree:
{ "component": "Text", "bind": "totalBudget" }
// ✗ INVALID — two inputs for same key
{ "component": "NumberInput", "bind": "totalBudget" }
{ "component": "Slider", "bind": "totalBudget" }Informative note: This constraint prevents conflicting write paths. If a future extension requires multiple input modalities for the same field, it should define explicit synchronization semantics.
When a component binds to a repeatable group item
(an item with type: "group" and
minRepeat/maxRepeat in the Definition), the
component acts as a repeat template.
The renderer MUST:
bind values
relative to the repeat context. Child keys are still flat item keys, but
they resolve within the current repeat instance.minRepeat and maxRepeat constraints
from the Definition.Repeatable group binding is available on DataTable (§6.13), where each repeat instance becomes a table row, and on Accordion (§6.3), where each repeat instance becomes a collapsible section.
Other layout and container components MUST NOT bind to repeatable groups. Processors MUST reject such bindings.
A Component Document is NOT required to bind every item in the
Definition. However, the renderer MUST ensure that all
required items (items whose Bind required
evaluates to true) are rendered and editable.
For required items that are NOT bound to any Input component in the tree:
presentation hints otherwise, or
renderer defaults as a last resort.For non-required items that are not bound, the renderer MAY omit them
entirely. If the Definition’s relevant expression for an
unbound item evaluates to true and the item is visible, the
renderer SHOULD render it using fallback rules.
Each Input component declares which Definition dataType
values it is compatible with. Binding a component to an item with an
incompatible dataType is a validation error.
dataType |
Compatible Input Components |
|---|---|
string |
TextInput |
number |
NumberInput, Slider, Rating |
integer |
NumberInput, Slider, Rating |
boolean |
Toggle |
date |
DatePicker |
dateTime |
DatePicker |
time |
DatePicker |
choice |
Select, RadioGroup |
multiChoice |
CheckboxGroup |
attachment |
FileUpload, Signature |
Notes:
number,
integer, and (via formatting) items that use
prefix/suffix for currency display.dataType when used in read-only mode via
bind.Processors MUST validate bind/dataType compatibility and MUST reject or warn on incompatible bindings.
This section defines the 17 Core components that all conforming processors MUST support. Components are grouped by category: Layout, Input, Display, and Container.
For each component, the specification provides:
Category: Layout Level: Core Accepts children: Yes Bind: Forbidden
A top-level page container representing a logical section of a form.
When formPresentation.pageMode is "wizard" or
"tabs", Pages define the navigation steps or tab panels.
Pages MAY also be used standalone within a Stack for single-page
sectioned forms.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
title |
string | — | No | Page heading displayed at the top of the section. |
description |
string | — | No | Subtitle or description text rendered below the title. |
<section> or equivalent).title is present, MUST render it as a heading
element.formPresentation.pageMode is
"wizard", the Page MUST be shown/hidden according to the
current step navigation state.{
"component": "Page",
"title": "Project Information",
"description": "Enter basic details about your project.",
"children": [
{ "component": "TextInput", "bind": "projectName" },
{ "component": "TextInput", "bind": "projectCode" }
]
}Category: Layout Level: Core Accepts children: Yes Bind: Forbidden
A flexbox-style stacking container that arranges its children in a vertical or horizontal sequence. Stack is the most common layout primitive and is typically used as the root component.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
direction |
string | "vertical" |
No | Stack direction. MUST be one of "vertical" or
"horizontal". |
gap |
string | number | 0 |
Yes | Spacing between children. String values (e.g., "16px",
"$token.spacing.md") or numeric pixel values. |
align |
string | "stretch" |
No | Cross-axis alignment. MUST be one of "start",
"center", "end", "stretch". |
wrap |
boolean | false |
No | Whether children wrap to the next line when direction
is "horizontal". |
gap between adjacent visible children.align to the cross-axis.wrap is true and direction is
horizontal, children MUST wrap to new rows when they exceed container
width.{
"component": "Stack",
"direction": "vertical",
"gap": "$token.spacing.md",
"children": [
{ "component": "TextInput", "bind": "firstName" },
{ "component": "TextInput", "bind": "lastName" }
]
}Category: Layout Level: Core Accepts children: Yes Bind: Forbidden
A multi-column grid layout that distributes children across columns. Children are placed in source order, wrapping to new rows as needed.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
columns |
integer | string | 2 |
Yes | Number of columns (integer) or a CSS grid-template-columns value
(string, e.g., "1fr 2fr 1fr"). |
gap |
string | number | 0 |
Yes | Spacing between grid cells. |
rowGap |
string | number | (inherits gap) |
Yes | Vertical spacing between rows, if different from
gap. |
{
"component": "Grid",
"columns": 3,
"gap": "$token.spacing.md",
"children": [
{ "component": "TextInput", "bind": "firstName" },
{ "component": "TextInput", "bind": "middleName" },
{ "component": "TextInput", "bind": "lastName" }
]
}The Wizard component type was removed in favor of
formPresentation.pageMode: "wizard" with a
Stack > Page* tree structure. See Core §4.1.2 for
normative page mode processing requirements. Wizard-style navigation is
now a presentation mode applied to a Stack of Pages, not a distinct
component type.
Category: Layout Level: Core Accepts children: No Bind: Forbidden
An empty spacing element that inserts visual space between siblings. Spacer is a leaf component with no children and no binding.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
size |
string | number | "$token.spacing.md" |
Yes | The amount of space. String values (e.g., "24px") or
numeric pixel values. |
children is present,
processors MUST ignore it.{ "component": "Spacer", "size": "$token.spacing.lg" }Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: string,
number (as text), date (as text),
time (as text), dateTime (as text)
A single-line or multi-line text input field. This is the default
input component for string-type fields. When maxLines is
greater than 1, the input renders as a multi-line textarea.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
placeholder |
string | — | No | Placeholder text displayed when the field is empty. |
maxLines |
integer | 1 |
No | Maximum visible lines. 1 = single-line input,
>1 = multi-line textarea. MUST be ≥ 1. |
inputMode |
string | "text" |
No | Input mode hint. One of "text", "email",
"tel", "url", "search". |
prefix |
string | — | No | Static text rendered before the input (e.g.,
"https://"). |
suffix |
string | — | No | Static text rendered after the input (e.g.,
".com"). |
<input type="text"> or <textarea>
for multi-line).required,
readOnly, and relevant state.inputMode as an input hint for virtual
keyboards.maxLength constraint, the
renderer SHOULD indicate the limit.{
"component": "TextInput",
"bind": "email",
"placeholder": "you@example.com",
"inputMode": "email"
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: integer,
number
A numeric input field with optional step controls. Suitable for integers, decimals, and monetary values (when paired with prefix/suffix).
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
step |
number | 1 |
No | Increment/decrement step value. |
min |
number | — | No | Minimum allowed value. |
max |
number | — | No | Maximum allowed value. |
showStepper |
boolean | false |
No | Whether to show increment/decrement buttons. |
locale |
string | — | No | Locale for number formatting (e.g., "en-US"). |
<input type="number"> or equivalent).required, readOnly, and
relevant from the bound item.min or max is specified, MUST
constrain the stepper controls accordingly.{
"component": "NumberInput",
"bind": "quantity",
"min": 1,
"max": 100,
"step": 1,
"showStepper": true
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: date,
dateTime, time
A date, datetime, or time picker control. The picker mode is
automatically determined by the bound item’s dataType.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
format |
string | — | No | Display format hint (e.g., "YYYY-MM-DD",
"MM/DD/YYYY"). Does not affect stored value (always ISO
8601). |
minDate |
string | — | No | Earliest selectable date (ISO 8601). |
maxDate |
string | — | No | Latest selectable date (ISO 8601). |
showTime |
boolean | false |
No | Whether to include time selection (relevant for
dateTime). |
date → date pickerdateTime → date + time pickertime → time pickerrequired, readOnly, and
relevant state.minDate or maxDate is specified, MUST
disable dates outside the range.{
"component": "DatePicker",
"bind": "startDate",
"format": "MM/DD/YYYY",
"minDate": "2025-01-01"
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: choice
A dropdown selection control. Options are read from the bound item’s
options array or optionSet reference in the
Definition.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
searchable |
boolean | false |
No | Whether to enable type-ahead search/filtering of options. |
placeholder |
string | "Select…" |
No | Placeholder text when no option is selected. |
clearable |
boolean | false |
No | Whether the user can clear the selection to null. |
options or
optionSet.label to the user and store the
option value in the data.required, readOnly, and
relevant state.searchable is true, MUST provide a
filter/search input within the dropdown.{
"component": "Select",
"bind": "department",
"searchable": true,
"placeholder": "Choose a department"
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: multiChoice
A group of checkboxes for multi-select fields. Options are read from
the bound item’s options or optionSet.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
columns |
integer | 1 |
No | Number of columns to arrange checkboxes in. |
selectAll |
boolean | false |
No | Whether to display a “Select All” control. |
required, readOnly, and
relevant state.selectAll is true, MUST provide a
master toggle control.{
"component": "CheckboxGroup",
"bind": "interests",
"columns": 2,
"selectAll": true
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: boolean
A boolean switch/toggle control. Suitable for yes/no, on/off, or true/false fields.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
onLabel |
string | "On" |
No | Label displayed when the toggle is in the true
state. |
offLabel |
string | "Off" |
No | Label displayed when the toggle is in the false
state. |
true or false in the data.required, readOnly, and
relevant state.onLabel/offLabel) for the current state.{
"component": "Toggle",
"bind": "agreeToTerms",
"onLabel": "I agree",
"offLabel": "I do not agree"
}Category: Input Level: Core
Accepts children: No Bind: Required
Compatible dataTypes: attachment
A file upload control for attachment-type fields. Supports single or multiple file selection with optional type and size constraints.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
accept |
string | "*/*" |
No | Accepted MIME types (comma-separated, e.g.,
"image/*,application/pdf"). |
maxSize |
integer | — | No | Maximum file size in bytes. |
multiple |
boolean | false |
No | Whether multiple files may be uploaded. |
dragDrop |
boolean | true |
No | Whether to display a drag-and-drop zone. |
accept MIME types when
the platform supports it.maxSize before
upload.required, readOnly, and
relevant state.multiple is true, MUST allow
selection of multiple files.{
"component": "FileUpload",
"bind": "supportingDocuments",
"accept": "application/pdf,image/*",
"maxSize": 10485760,
"multiple": true
}Category: Display Level: Core Accepts children: No Bind: Forbidden
A section heading element. Used to structure the visual hierarchy of the form. Heading is purely presentational and does not bind to data.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
level |
integer | — (REQUIRED) | No | Heading level, 1–6. MUST correspond to HTML heading semantics
(<h1>–<h6>). |
text |
string | — (REQUIRED) | No | The heading text content. |
text content.bind property. If present, processors
MUST ignore it.{ "component": "Heading", "level": 2, "text": "Budget Details" }Category: Display Level: Core Accepts children: No Bind: Optional
A block of static or data-bound text. When bind is
present, displays the bound item’s current value as read-only text. When
bind is absent, displays the static text
prop.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
text |
string | "" |
No | Static text content. Ignored when bind is present. |
format |
string | "plain" |
No | Text format. MUST be one of "plain" or
"markdown". When "markdown", renderers SHOULD
render basic Markdown formatting (bold, italic, links, lists). |
bind is present, MUST display the bound item’s
formatted value. The renderer SHOULD apply appropriate formatting based
on the item’s dataType (e.g., date formatting, number
formatting).format is "markdown", MUST render
basic Markdown. Renderers MUST sanitize Markdown output to prevent
script injection.// Static text
{ "component": "Text", "text": "Please review before submitting.", "format": "markdown" }
// Bound text
{ "component": "Text", "bind": "totalBudget" }Category: Display Level: Core Accepts children: No Bind: Forbidden
A horizontal rule used to visually separate sections of the form.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
label |
string | — | No | Optional label text centered on the divider line. |
<hr> or
equivalent).label is present, MUST display the text centered
on or adjacent to the rule.bind property.{ "component": "Divider", "label": "Section Break" }Category: Container Level: Core Accepts children: Yes Bind: Forbidden
A bordered surface that visually groups related content. Cards provide a visual boundary with optional title and subtitle.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
title |
string | — | No | Card header title. |
subtitle |
string | — | No | Card header subtitle, rendered below the title. |
elevation |
string | "low" |
Yes | Shadow depth. SHOULD map to a token (e.g.,
"$token.elevation.low"). |
title is present, MUST render a card header.{
"component": "Card",
"title": "Contact Information",
"children": [
{ "component": "TextInput", "bind": "email" },
{ "component": "TextInput", "bind": "phone" }
]
}Category: Container Level: Core Accepts children: Yes Bind: Forbidden
An expandable/collapsible section. The user can toggle visibility of the children. Useful for optional sections or advanced options.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
title |
string | — (REQUIRED) | No | The collapsible section header. MUST be visible regardless of open/closed state. |
defaultOpen |
boolean | false |
No | Whether the section is initially expanded. |
title in the header.relevant and when
state MUST still be evaluated.aria-expanded,
etc.).{
"component": "Collapsible",
"title": "Advanced Options",
"defaultOpen": false,
"children": [
{ "component": "Toggle", "bind": "enableNotifications" },
{ "component": "Select", "bind": "notificationFrequency" }
]
}Category: Container Level: Core Accepts children: Yes Bind: Forbidden
A container whose visibility is controlled by a
required when expression. Unlike the
optional when property available on all components (§8),
ConditionalGroup makes the condition its primary purpose — it exists
solely to conditionally show/hide a group of children.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
when |
string (FEL) | — (REQUIRED) | No | FEL boolean expression. When false or
null, the group and all children are hidden.
REQUIRED on ConditionalGroup (unlike optional
when on other components). |
fallback |
string | — | No | Optional text to display when the condition is
false. |
when expression against the data
tree.true, MUST render all
children.false or
null, MUST hide all children. If fallback text
is present, MUST display it in place of the hidden children.relevant, which may clear
data).when expression is
invalid. Processors MUST reject such documents.{
"component": "ConditionalGroup",
"when": "$hasEmployer = true",
"fallback": "Employer details are not required for this application type.",
"children": [
{ "component": "TextInput", "bind": "employerName" },
{ "component": "TextInput", "bind": "employerAddress" }
]
}This section defines the 16 Progressive components. A Complete Conformant processor MUST support all 16. A Core Conformant processor MUST substitute the specified Core fallback for each Progressive component and SHOULD emit an informative warning.
Each Progressive component entry includes a Fallback line identifying the Core component that replaces it in Core-level processors. §6.17 provides a consolidated fallback table.
Category: Layout Level: Progressive Accepts children: Yes Bind: Forbidden Fallback: Grid
An explicit multi-column layout where each child occupies a column
whose width is specified by the widths array. Unlike Grid,
which auto-distributes children into equal cells, Columns gives precise
control over per-column sizing.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
widths |
array of strings | equal widths | No | Per-child column widths as CSS values (e.g.,
["1fr", "2fr", "1fr"] or ["200px", "auto"]).
Array length SHOULD match the number of children. |
gap |
string | number | 0 |
Yes | Spacing between columns. |
widths length differs from children count, MUST
distribute remaining children into equal-width columns.Core processors MUST replace Columns with a Grid
whose columns prop equals the number of children. The
gap prop is preserved.
{
"component": "Columns",
"widths": ["2fr", "1fr"],
"gap": "$token.spacing.md",
"children": [
{ "component": "TextInput", "bind": "address" },
{ "component": "TextInput", "bind": "zipCode" }
]
}Category: Layout Level: Progressive Accepts children: Yes Bind: Forbidden Fallback: Stack (each child preceded by a Heading)
A tabbed navigation container. Each direct child represents the
content of one tab. Tab labels are derived from child Page
title props or from the tabLabels array.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
position |
string | "top" |
No | Tab bar position. MUST be one of "top",
"bottom", "left", or
"right". |
tabLabels |
array of strings | — | No | Explicit tab labels. When absent, the renderer reads
title from each child (children SHOULD be Page
components). |
defaultTab |
integer | 0 |
No | Zero-based index of the initially active tab. |
Core processors MUST replace Tabs with a Stack
(direction "vertical"). Each child is preceded by a
Heading (level 3) whose text is the corresponding tab
label. All children are rendered visibly in sequence.
{
"component": "Tabs",
"tabLabels": ["Personal", "Employment", "Review"],
"children": [
{ "component": "Stack", "children": [
{ "component": "TextInput", "bind": "firstName" },
{ "component": "TextInput", "bind": "lastName" }
]},
{ "component": "Stack", "children": [
{ "component": "TextInput", "bind": "employer" }
]},
{ "component": "Stack", "children": [
{ "component": "Text", "text": "Please review your information." }
]}
]
}Category: Layout Level: Progressive Accepts children: Yes Bind: Optional (repeatable group key) Fallback: Stack with Collapsible children
A vertical list of collapsible sections where, by default, only one
section is expanded at a time. Each child SHOULD be a component with a
title prop (e.g., Page, Card, Collapsible) to serve as the
section header.
When bind is provided, it MUST reference a repeatable
group item. Each repeat instance becomes one accordion section. Child
bind values resolve relative to the repeat context.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
allowMultiple |
boolean | false |
No | Whether multiple sections may be expanded simultaneously. When
false, expanding one section collapses the others. |
defaultOpen |
integer | 0 |
No | Zero-based index of the initially expanded section. |
labels |
string[] | — | No | Section header labels. labels[i] is the summary text
for children[i]. Falls back to "Section {i+1}"
when absent. |
allowMultiple is false, MUST enforce
mutual exclusion (only one open at a time).role="region",
aria-expanded, etc.).Core processors MUST replace Accordion with a Stack
where each child is wrapped in a Collapsible. The first
child’s Collapsible has defaultOpen: true; the rest have
defaultOpen: false.
{
"component": "Accordion",
"allowMultiple": false,
"children": [
{ "component": "Page", "title": "Section A", "children": [
{ "component": "TextInput", "bind": "fieldA" }
]},
{ "component": "Page", "title": "Section B", "children": [
{ "component": "TextInput", "bind": "fieldB" }
]}
]
}Category: Input Level: Progressive
Accepts children: No Bind: Required
Compatible dataTypes: choice
Fallback: Select
A group of radio buttons for single-select choice fields. All options are visible simultaneously, making RadioGroup suitable for short option lists (typically ≤ 7 items).
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
columns |
integer | 1 |
No | Number of columns to arrange radio buttons in. |
orientation |
string | "vertical" |
No | Layout direction. MUST be "vertical" or
"horizontal". |
options or optionSet.required, readOnly, and
relevant state.Core processors MUST replace RadioGroup with Select.
The searchable prop defaults to false. The
columns prop is discarded.
{
"component": "RadioGroup",
"bind": "priority",
"columns": 3,
"orientation": "horizontal"
}Category: Input Level: Progressive
Accepts children: No Bind: Required
Compatible dataTypes: number,
integer Fallback: NumberInput
A currency-aware numeric input that displays a currency symbol and formatted number. Stores the raw numeric value without currency formatting.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
currency |
string | "USD" |
No | ISO 4217 currency code (e.g., "USD",
"EUR", "GBP"). |
showCurrency |
boolean | true |
No | Whether to display the currency symbol. |
locale |
string | — | No | Locale for number/currency formatting (e.g.,
"en-US"). |
required, readOnly, and
relevant state.Core processors MUST replace MoneyInput with
NumberInput. The currency symbol SHOULD be rendered as
a prefix label adjacent to the input if the bound item has a
prefix presentation hint.
{
"component": "MoneyInput",
"bind": "totalBudget",
"currency": "USD",
"showCurrency": true
}Category: Input Level: Progressive
Accepts children: No Bind: Required
Compatible dataTypes: integer,
number Fallback: NumberInput
A range slider control for selecting a numeric value within a continuous range.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
min |
number | 0 |
No | Minimum value. |
max |
number | 100 |
No | Maximum value. |
step |
number | 1 |
No | Step increment. |
showValue |
boolean | true |
No | Whether to display the current numeric value adjacent to the slider. |
showTicks |
boolean | false |
No | Whether to display tick marks at step intervals. |
min–max
range.step increments.showValue is true, MUST display the
current value.required, readOnly, and
relevant state.Core processors MUST replace Slider with
NumberInput. The min, max,
and step props are preserved on the NumberInput.
{
"component": "Slider",
"bind": "satisfaction",
"min": 1,
"max": 10,
"step": 1,
"showValue": true
}Category: Input Level: Progressive
Accepts children: No Bind: Required
Compatible dataTypes: integer
Fallback: NumberInput
A star (or icon) rating control for selecting an integer value within a small range (typically 1–5 or 1–10).
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
max |
integer | 5 |
No | Maximum rating value (number of stars/icons). |
icon |
string | "star" |
No | Icon type. Well-known values: "star",
"heart", "circle". Renderers MAY support
additional icons. |
allowHalf |
boolean | false |
No | Whether half-star values are allowed (stored as decimal, e.g.,
3.5). |
max icon elements.allowHalf) in the data.required, readOnly, and
relevant state.Core processors MUST replace Rating with NumberInput
with min: 1, max preserved, and
step: 1.
{
"component": "Rating",
"bind": "serviceRating",
"max": 5,
"icon": "star"
}Category: Input Level: Progressive
Accepts children: No Bind: Required
Compatible dataTypes: attachment
Fallback: FileUpload
A signature capture pad that records a drawn signature as an image attachment.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
strokeColor |
string | "#000000" |
Yes | Stroke color for the signature pen. |
height |
integer | 150 |
No | Height of the signature pad in pixels. |
penWidth |
number | 2 |
No | Stroke width in pixels. |
clearable |
boolean | true |
No | Whether to show a clear/reset control. |
required, readOnly, and
relevant state.Core processors MUST replace Signature with
FileUpload with accept: "image/*".
{
"component": "Signature",
"bind": "approverSignature",
"strokeColor": "#000",
"height": 200
}Category: Display Level: Progressive Accepts children: No Bind: Forbidden Fallback: Text (with severity prefix)
A status message block used for informational banners, warnings, error summaries, or success messages.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
severity |
string | — (REQUIRED) | No | Alert severity level. MUST be one of "info",
"success", "warning",
"error". |
text |
string | — (REQUIRED) | No | Alert message text. |
dismissible |
boolean | false |
No | Whether the user can dismiss the alert. |
role="alert" for
error/warning, role="status" for info/success).text content.Core processors MUST replace Alert with Text. The
text prop is prefixed with the severity in brackets: e.g.,
"[Warning] " + original text.
{
"component": "Alert",
"severity": "warning",
"text": "Budget exceeds department limit. Approval required."
}Category: Display Level: Progressive Accepts children: No Bind: Forbidden Fallback: Text
A small label badge for status indicators, counts, or tags.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
text |
string | — (REQUIRED) | No | Badge label text. |
variant |
string | "default" |
No | Visual variant. Well-known values: "default",
"primary", "success", "warning",
"error". |
variant.Core processors MUST replace Badge with Text using
the same text prop.
{ "component": "Badge", "text": "Draft", "variant": "warning" }Category: Display Level: Progressive Accepts children: No Bind: Optional Fallback: Text (showing “X / Y”)
A visual progress indicator. When bound, reads the current value from
the data. When unbound, uses the static value prop.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
value |
number | 0 |
No | Current progress value. Ignored when bind is
present. |
max |
number | 100 |
No | Maximum value (100% completion). |
label |
string | — | No | Accessible label for the progress bar. |
showPercent |
boolean | true |
No | Whether to display the percentage text. |
<progress>
or equivalent).value / max * 100.aria-valuenow, aria-valuemin,
and aria-valuemax.Core processors MUST replace ProgressBar with Text
displaying the progress as text, e.g.,
"75 / 100 (75%)".
{
"component": "ProgressBar",
"bind": "completionScore",
"max": 100,
"label": "Form completion"
}Category: Display Level: Progressive Accepts children: No Bind: Forbidden Fallback: Stack of Text components
A key-value summary display that shows multiple field labels and their current values in a structured list. Useful for review pages.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
items |
array | — (REQUIRED) | No | Array of summary items. Each element is an object with
label (string, REQUIRED), bind (string,
REQUIRED — item key), and optional optionSet (string). |
Each item object in the items array supports:
| Item Field | Type | Required | Description |
|---|---|---|---|
label |
string | Yes | Display label shown next to the value. |
bind |
string | Yes | Path to the field whose value to display. |
optionSet |
string | No | Name of an option set defined in the form definition. When present,
the raw bound value is resolved to its display label via the named
option set. Use for choice and multiChoice
fields. |
items, MUST display the
label and the current value of the bound item.dataType.optionSet is set on an item, renderers MUST look
up the bound value in the named option set and display the matching
label. If no match is found, the raw value SHOULD be
displayed.Core processors MUST replace Summary with a Stack
containing one Text component per item, with
text set to
"<label>: <value>".
{
"component": "Summary",
"items": [
{ "label": "Project Name", "bind": "projectName" },
{ "label": "Total Budget", "bind": "totalBudget" },
{ "label": "Organization Type", "bind": "orgType", "optionSet": "orgTypes" }
]
}Category: Display Level: Progressive Accepts children: No Bind: Optional (binds to a repeatable group) Fallback: Stack of bound items
A tabular display of repeatable group data. Each repeat instance
becomes a row; each column displays a field within the repeat. DataTable
is one of the few non-Layout/Container components that MAY use
bind to reference a repeatable group.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
columns |
array | — (REQUIRED) | No | Array of column definitions. Each element is an object with
header (string, REQUIRED) and bind (string,
REQUIRED — item key within the repeat group). |
showRowNumbers |
boolean | false |
No | Whether to display row numbers. |
allowAdd |
boolean | true |
No | Whether to show an “Add row” control. |
allowRemove |
boolean | true |
No | Whether to show per-row “Remove” controls. |
allowAdd is true, MUST provide an
“Add” affordance, subject to maxRepeat constraints.allowRemove is true, MUST provide
per-row “Remove” affordances, subject to minRepeat
constraints.Core processors MUST replace DataTable with a Stack that repeats a Card for each repeat instance. Within each Card, bound fields are rendered as TextInput or appropriate Core components.
{
"component": "DataTable",
"bind": "lineItems",
"columns": [
{ "header": "Description", "bind": "description" },
{ "header": "Amount", "bind": "amount" },
{ "header": "Category", "bind": "category" }
]
}Category: Container Level: Progressive Accepts children: Yes Bind: Forbidden Fallback: Card
A side panel used for supplementary content, help text, or contextual actions. Panels may be positioned alongside the main content.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
position |
string | "left" |
No | Panel position. MUST be one of "left" or
"right". |
title |
string | — | No | Panel header title. |
width |
string | "300px" |
Yes | Panel width. |
position property.Core processors MUST replace Panel with Card. The
title prop is preserved. The position and
width props are discarded.
{
"component": "Panel",
"position": "left",
"title": "Help",
"width": "280px",
"children": [
{ "component": "Text", "text": "Need help? Contact support." }
]
}Category: Container Level: Progressive Accepts children: Yes Bind: Forbidden Fallback: Collapsible
A dialog overlay that displays content in a modal window above the main form. Modals require explicit user action to open and close.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
title |
string | — (REQUIRED) | No | Modal dialog title. |
size |
string | "md" |
No | Modal size. MUST be one of "sm", "md",
"lg", "xl", or "full". |
trigger |
string | "button" |
No | How the modal is triggered. MUST be "button" (a
dedicated open button) or "auto" (opens automatically based
on when). |
triggerLabel |
string | "Open" |
No | Label for the trigger button when trigger is
"button". |
closable |
boolean | true |
No | Whether the modal can be dismissed by the user. |
closable is true.role="dialog" and
aria-modal="true".Core processors MUST replace Modal with Collapsible.
The title prop is preserved. The modal’s content is
rendered as the collapsible body, initially collapsed
(defaultOpen: false).
{
"component": "Modal",
"title": "Terms and Conditions",
"trigger": "button",
"triggerLabel": "View Terms",
"children": [
{ "component": "Text", "text": "By submitting this form, you agree to...", "format": "markdown" }
]
}Category: Container Level: Progressive Accepts children: Yes Bind: Forbidden Fallback: Collapsible
A lightweight anchored overlay that shows contextual content when the trigger is activated.
| Prop | Type | Default | Token-able | Description |
|---|---|---|---|---|
triggerBind |
string | — | No | Optional bind key whose current value is shown as the trigger label. |
triggerLabel |
string | "Open" |
No | Fallback trigger label when triggerBind has no
value. |
placement |
string | "bottom" |
No | Preferred popover placement. MUST be one of "top",
"right", "bottom", or
"left". |
children inside the content surface.Core processors MUST replace Popover with
Collapsible. The triggerLabel value SHOULD
map to Collapsible title. The placement
property is discarded.
{
"component": "Popover",
"triggerBind": "projectName",
"triggerLabel": "Show details",
"placement": "right",
"children": [
{ "component": "Text", "text": "Additional context for this field." }
]
}The following table defines the complete set of Progressive → Core fallback substitutions. A Core Conformant processor MUST apply these fallbacks when it encounters a Progressive component.
| Progressive Component | Core Fallback | Notes |
|---|---|---|
| Columns | Grid | columns set to child count; gap
preserved. |
| Tabs | Stack + Heading | Each child preceded by a Heading (level 3) with the tab label. |
| Accordion | Stack + Collapsible | Each child wrapped in Collapsible; first defaults open. |
| RadioGroup | Select | columns discarded. |
| MoneyInput | NumberInput | Currency symbol rendered as prefix if available. |
| Slider | NumberInput | min, max, step
preserved. |
| Rating | NumberInput | min: 1, max preserved,
step: 1. |
| Signature | FileUpload | accept set to "image/*". |
| Alert | Text | Text prefixed with severity in brackets. |
| Badge | Text | Same text prop. |
| ProgressBar | Text | Text shows
"<value> / <max> (<percent>%)". |
| Summary | Stack of Text | One Text per item: "<label>: <value>". |
| DataTable | Stack of Card | One Card per repeat instance with child inputs. |
| Panel | Card | title preserved; position/width discarded. |
| Modal | Collapsible | title preserved; defaultOpen: false. |
| Popover | Collapsible | triggerLabel mapped to title; placement
discarded. |
Fallback substitution MUST preserve:
when property (transferred to the fallback
component).responsive property (transferred if applicable
props exist on the fallback).style property.bind property (when the fallback supports it).Fallback substitution MUST discard props that have no equivalent on the Core fallback component. Processors SHOULD emit a warning listing discarded props.
The custom component mechanism allows authors to define reusable component subtrees with parameterized interpolation. Custom components promote consistency and reduce duplication in large component trees.
The top-level components property is an object whose
keys are custom component names and whose values are component
template objects.
Each template object has the following properties:
| Property | Type | Cardinality | Description |
|---|---|---|---|
params |
array of strings | 0..1 (OPTIONAL) | Parameter names accepted by this template. Each name MUST match
[a-zA-Z][a-zA-Z0-9_]*. |
tree |
object | 1..1 (REQUIRED) | The component subtree that is instantiated when this custom component is used. |
Custom component names MUST match [A-Z][a-zA-Z0-9]*
(PascalCase, starting with uppercase). Names MUST NOT collide with
built-in component names (§5, §6). Names beginning with x-
are reserved for vendor extensions (§13.3).
Example registry:
{
"components": {
"LabeledField": {
"params": ["field", "label"],
"tree": {
"component": "Stack",
"gap": "$token.spacing.sm",
"children": [
{ "component": "Heading", "level": 4, "text": "{label}" },
{ "component": "TextInput", "bind": "{field}" }
]
}
},
"AddressBlock": {
"params": ["prefix"],
"tree": {
"component": "Card",
"title": "Address",
"children": [
{ "component": "TextInput", "bind": "{prefix}Street" },
{ "component": "TextInput", "bind": "{prefix}City" },
{ "component": "TextInput", "bind": "{prefix}State" },
{ "component": "TextInput", "bind": "{prefix}Zip" }
]
}
}
}
}Parameter interpolation uses {paramName} syntax within
string-valued props. The following ABNF grammar defines the
interpolation syntax:
interpolated-string = *( literal-segment / param-ref / escaped-brace )
literal-segment = 1*( %x00-7A / %x7C-7C / %x7E-10FFFF ) ; any char except { }
param-ref = "{" param-name "}"
param-name = ALPHA *( ALPHA / DIGIT / "_" )
escaped-brace = "{{" / "}}"
Rules:
{paramName} is replaced with the corresponding
parameter value from the instantiation’s params
object.{{ produces a literal { in the
output.}} produces a literal } in the
output.{outer_{inner}} is
invalid.{name} (where name is not
in the template’s params array) MUST cause a validation
error.Interpolation is permitted in the following prop types ONLY:
| Allowed | Examples |
|---|---|
bind |
"bind": "{prefix}Street" |
when |
"when": "${field} != null" |
text (on Text, Heading, Alert, Badge) |
"text": "Address for {label}" |
title (on Page, Card, Collapsible, etc.) |
"title": "{sectionTitle}" |
placeholder |
"placeholder": "Enter {label}" |
label (on Divider) |
"label": "{section}" |
fallback (on ConditionalGroup) |
"fallback": "No {item} available" |
Interpolation is FORBIDDEN in:
| Forbidden | Reason |
|---|---|
component |
Component type switching creates ambiguous trees. |
$token.* references |
Token resolution is a separate phase. |
Numeric props (min, max,
step, columns, etc.) |
Type safety. |
Boolean props (searchable, showProgress,
etc.) |
Type safety. |
style keys or values |
Style resolution is a separate phase. |
A custom component is instantiated by using its registry name as the
component value and providing parameter values in a
params object:
{
"component": "AddressBlock",
"params": { "prefix": "home" }
}The processor MUST:
components
registry.params object. Missing params MUST cause a
validation error.tree.{paramName} occurrences in allowed string
props with the corresponding values from params.Extra params (keys in the instantiation’s params that
are not declared in the template’s params array) MUST be
ignored. Processors SHOULD emit a warning.
The instantiation MAY also include when,
style, and responsive props. These are applied
to the root of the resolved subtree (merged on top of
whatever the template already defines).
Custom component templates MUST NOT reference themselves, directly or indirectly. A cycle occurs when template A’s tree instantiates template B, and template B’s tree instantiates template A (or any longer chain that forms a loop).
Processors MUST perform static cycle detection at validation time by building a directed graph of template references and checking for cycles. Cycle detection MUST be performed regardless of parameter values — the analysis is structural, not data-dependent.
Documents containing recursive custom components MUST be rejected.
Custom component nesting (template A instantiates template B which instantiates template C) SHOULD NOT exceed 3 levels of custom component expansion.
The total tree depth (including both built-in and expanded custom components) SHOULD NOT exceed 20 levels.
Processors MAY enforce stricter limits. Processors MUST NOT enforce limits lower than 3 levels of custom nesting or 10 levels of total tree depth.
The when property enables components to be conditionally
shown or hidden based on the current data state, without affecting the
data model.
The when property is a FEL boolean
expression (fel-grammar.md). When present on a component:
true, the component (and
all its children) MUST be rendered.false,
null, or any non-boolean value, the component (and all its
children) MUST be hidden.The when property is OPTIONAL on all components except
ConditionalGroup (§5.18), where it is REQUIRED.
Multiple when conditions do NOT chain — each component
has at most one when expression. To express compound
conditions, use FEL logical operators within the expression:
{ "component": "TextInput", "bind": "spouseName",
"when": "$maritalStatus = 'married' and $age >= 18" }The when property and the Definition Bind’s
relevant expression serve different
purposes:
| Aspect | when (Component) |
relevant (Bind) |
|---|---|---|
| Scope | Visual presentation only | Data model inclusion |
| Data effect | None — hidden component’s bound data is preserved | Non-relevant data MAY be removed from the Instance (per
nonRelevantBehavior) |
| Defined in | Component Document (Tier 3) | Definition (Tier 1, Binds) |
| Evaluation | FEL against data tree | FEL against data tree |
| Override | Cannot override relevant |
Cannot be overridden by when |
When BOTH when and relevant apply to the
same bound item:
relevant is false, the component is
hidden regardless of when. The Bind
relevant takes precedence.relevant is true and when
is false, the component is hidden but the data remains in
the Instance.true, the component is visible.FEL expressions in when ALWAYS resolve against the
data tree (the Formspec Instance):
$fieldKey resolves to the current value of the field
with that key.@index resolves to the 1-based repeat index when inside
a repeatable group context.@count resolves to the total number of repeat instances
when inside a repeatable group context.FEL expressions MUST NOT reference component props, component state, or presentation-layer values. The evaluation context is strictly the data model.
When a when expression is malformed
(syntax error, unresolved function, type error):
false).when evaluation error.When a when expression references an item key that does
not exist in the Definition:
null per FEL
semantics.The responsive system allows component props to vary by viewport width, enabling adaptive layouts from a single Component Document.
Breakpoints are declared in the top-level breakpoints
object. Each key is a breakpoint name; each value is the minimum
viewport width in pixels at which that breakpoint activates.
{
"breakpoints": {
"sm": 576,
"md": 768,
"lg": 1024,
"xl": 1280
}
}Breakpoint names MUST be non-empty strings. Values MUST be non-negative integers. The same breakpoint format is used in the Theme Specification (theme-spec §6.4).
When a Component Document and a Theme Document both declare
breakpoints for the same Definition, the Component
Document’s breakpoints take precedence.
The responsive property on a component object is a JSON
object whose keys are breakpoint names and whose values are prop
override objects:
{
"component": "Grid",
"columns": 3,
"gap": "$token.spacing.md",
"responsive": {
"sm": { "columns": 1, "gap": "$token.spacing.sm" },
"md": { "columns": 2 }
}
}Override objects contain component-specific props only (not base props). The following properties MUST NOT appear in responsive overrides:
component — type switching is forbidden (§9.4).bind — data binding is viewport-independent.when — conditions are viewport-independent.children — tree structure is viewport-independent.responsive — recursive responsive is forbidden.The style property MAY appear in responsive
overrides.
Responsive overrides follow a mobile-first cascade:
style override replaces the entire
style object for that breakpoint.Resolution algorithm:
function resolveProps(component, viewportWidth, breakpoints):
resolved = copy(component.baseProps)
// Sort breakpoints by minWidth ascending
sorted = sortByValue(breakpoints)
for each (name, minWidth) in sorted:
if viewportWidth >= minWidth:
if component.responsive[name] exists:
shallowMerge(resolved, component.responsive[name])
return resolved
Example: with breakpoints {"sm": 576, "md": 768} and the
Grid example above, at viewport width 700px: - Base:
columns: 3, gap: "$token.spacing.md" - After
sm (576 ≤ 700):
columns: 1, gap: "$token.spacing.sm" - md does
not apply (768 > 700) - Result:
columns: 1, gap: "$token.spacing.sm"
Responsive overrides MUST NOT alter the structural identity of a component:
component type MUST NOT change per breakpoint.children array MUST NOT change per breakpoint (no
adding, removing, or reordering children).bind property MUST NOT change per breakpoint.These constraints ensure that the component tree is structurally stable across all viewport widths. Only presentational props (layout, spacing, visibility hints) may vary.
Component Documents support design tokens for visual consistency. Tokens defined in a Component Document follow the same format as Tokens in the Theme Specification (theme-spec §3).
The tokens object is a flat key-value map. Keys are
dot-delimited names; values are strings or numbers.
{
"tokens": {
"color.primary": "#0057B7",
"color.error": "#D32F2F",
"spacing.sm": "8px",
"spacing.md": "16px",
"spacing.lg": "24px",
"border.radius": "6px"
}
}Token keys MUST be non-empty strings. Token values MUST be strings or numbers. Tokens MUST NOT contain nested objects, arrays, booleans, or null.
The token format is identical to theme-spec §3.1. The RECOMMENDED
category prefixes (color., spacing.,
typography., border., elevation.,
x-) from theme-spec §3.2 apply here as well.
Tokens are referenced in style objects and token-able
props using the $token. prefix:
$token.<key>
Examples: - "gap": "$token.spacing.md" resolves to
"16px". -
"style": { "borderRadius": "$token.border.radius" }
resolves to "6px".
The reference syntax is identical to theme-spec §3.3. Token
references MUST NOT be recursive (a token value MUST NOT itself contain
a $token. reference).
When both a Component Document and a Theme Document define tokens for the same Definition, the following cascade applies:
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | Component Document tokens |
Tier 3 tokens. |
| 2 | Theme Document tokens |
Tier 2 tokens. |
| 3 (lowest) | Renderer defaults | Platform/implementation defaults. |
Resolution:
$token.key reference is encountered in the
Component Document, look up key in the Component Document’s
tokens.key in the Theme Document’s
tokens.This cascade allows Component Documents to override specific theme tokens while inheriting the rest.
When a $token. reference cannot be resolved through the
cascade (not found in Component tokens, Theme tokens, or renderer
defaults):
Web renderers SHOULD emit resolved theme tokens as CSS custom properties on the form’s root container element. The recommended naming convention is:
--formspec-{token-key-with-dots-replaced-by-hyphens}
For example, a theme token color.primary with value
#005ea2 SHOULD be emitted as:
--formspec-color-primary: #005ea2;This enables external CSS — including design-system bridge
stylesheets and author-defined overrides — to reference theme tokens
without JavaScript coupling. Bridge CSS can use
var(--formspec-color-primary) to stay in sync with the
active theme.
Renderers that emit CSS custom properties SHOULD update them when the
theme document changes. Renderers MAY also emit tokens from the
Component Document’s tokens map, with component tokens
taking precedence over theme tokens for identically named
properties.
Non-web renderers (PDF, native) MAY ignore this convention entirely.
This section defines how Tier 3 (Component Documents) interacts with Tier 2 (Themes) and Tier 1 (Definition presentation hints) in a multi-tier presentation stack.
When an item in the Definition is NOT bound to any component in the tree, the renderer falls back to lower tiers:
presentation hints (core §4.2.5) to select a
widget and configure rendering.dataType.Fallback rendering for unbound items follows the rules in §4.5.
Component Documents inherit tokens from an associated Theme Document.
When a Component Document references $token.color.primary
and does NOT define that token in its own tokens map, the
resolution falls through to the Theme’s tokens map
(§10.3).
This enables a common pattern: the Theme defines the design system tokens (colors, spacing, typography), and the Component Document references them without redeclaring.
A Component Document MAY override specific tokens to customize the appearance without diverging from the theme entirely.
The general precedence rule for all presentation decisions:
| Priority | Tier | Effect |
|---|---|---|
| 1 (highest) | Tier 3 — Component Document | Component tree layout, component selection, style, and tokens override everything below. |
| 2 | Tier 2 — Theme Document | Widget configuration, selector cascade, tokens, and page layout apply to items NOT controlled by Tier 3. Tier 2 tokens are inherited by Tier 3 (§10.3). |
| 3 (lowest) | Tier 1 — Definition hints | Inline presentation and formPresentation
hints serve as baseline defaults. |
Specific interactions:
widgetHint.label is
the source of truth. Context-specific labels use the labels
map on the Definition item.required,
readOnly, relevant, constraint,
and calculate from the Definition are
never overridden by any presentation tier. They always
apply.A Component Document is NOT required to bind every item in the Definition. A partial tree binds only a subset of items. The remaining items are rendered via Tier 2/Tier 1 fallback (§11.1, §4.5).
This enables incremental adoption: an author can create a Component Document that controls the layout of key sections while allowing simpler fields to render automatically.
The renderer MUST:
This section defines the validation requirements for Component Documents and the conformance criteria for processors.
A conforming processor MUST validate a Component Document against the
structural rules defined in this specification. These rules MAY be
expressed as a JSON Schema (component.schema.json) for
tooling purposes.
Structural validation MUST verify:
$formspecComponent, version,
targetDefinition, and tree are present.direction, align, severity,
position, etc.) contain valid values.component value
is either a built-in name (§5, §6) or a key in the
components registry.children property.when:
ConditionalGroup components include a when property.level is 1–6 and
text is present.Structural validation MUST be performed before referential integrity checks (§12.2).
After structural validation passes, processors MUST verify referential integrity:
Bind keys: Every bind value MUST
correspond to an item key in the target Definition. Unknown
bind keys MUST produce a warning. Processors SHOULD reject documents
with bind keys that reference non-existent items, or MAY treat them as
non-fatal warnings.
Token references: Every $token.key
reference SHOULD resolve to a token in the Component Document’s
tokens map, the Theme Document’s tokens map,
or be a well-known renderer default. Unresolvable token references MUST
produce a warning (§10.4).
Custom component references: Every
component value that is not a built-in name MUST exist as a
key in the components registry. References to undefined
custom components MUST be rejected.
Custom component params: When instantiating a
custom component, every param declared in the template’s
params array MUST have a corresponding entry in the
instantiation’s params object. Missing params MUST be
rejected.
Summary and DataTable bind refs: The
bind values within Summary items and DataTable
columns MUST reference valid item keys.
Cycle-free custom components: The custom component dependency graph MUST be acyclic (§7.4).
Processors MUST verify that each Input component’s bound item has a
compatible dataType, per the matrix in §4.6:
dataType in the
Definition.Incompatible bindings SHOULD be treated as errors. Processors MAY continue rendering with a warning, using the component as-is and relying on the renderer’s type coercion, but this behavior is NOT RECOMMENDED.
A processor declares conformance at one of two levels:
Core Conformant:
when expressions (§8).responsive prop overrides (§9).$token. references (§10).Complete Conformant:
Processors SHOULD declare their conformance level in their documentation.
Formspec Component Documents are intentionally constrained to maintain predictability, portability, and ease of implementation. This section catalogues excluded features and the guard rails that keep Component Documents declarative.
The following features are explicitly excluded from this specification. They MUST NOT be implemented as normative behavior by conforming processors.
| Excluded Feature | Rationale |
|---|---|
| Imperative event handlers / scripting | Component Documents are declarative data, not programs. No
onClick, onChange, or embedded JavaScript/FEL
imperative code. |
| Conditional component type switching | The component prop MUST NOT vary by condition,
breakpoint, or parameter. Structural ambiguity prevents static
analysis. |
| Recursive custom components | Self-referencing templates produce unbounded trees. Prohibited and statically detected (§7.4). |
| Computed props via FEL | FEL is used ONLY in when conditions and display
text interpolation. Props like columns,
min, max MUST NOT be FEL expressions. |
| Arbitrary slot projection / transclusion | Components do not have named slots or content projection beyond
children. This avoids the complexity of Angular/Vue-style
slot APIs. |
| Animation specifications | Transitions, keyframes, and timing functions are out of scope. Renderers MAY animate independently. |
| Server-side data fetching | Component Documents MUST NOT trigger HTTP requests, API calls, or data loading. All data is provided by the Formspec Instance. |
| Component inheritance | No extends or prototype-chain mechanism for component
types. Use custom components (§7) for reuse. |
| Dynamic component registration | The components registry is static. Components MUST NOT
be added or removed at runtime. |
| Deep responsive (children swap) | Responsive overrides MUST NOT alter children,
bind, or component type (§9.4). Only
presentational props may vary. |
The following limits protect processors and authors from excessive complexity:
| Guard Rail | Limit | Enforcement |
|---|---|---|
| Total tree depth | SHOULD NOT exceed 20 levels. | Processors MAY reject deeper trees. MUST NOT enforce limits below 10. |
| Custom component nesting | SHOULD NOT exceed 3 levels of expansion. | Processors MAY reject deeper nesting. |
Single when per component |
Each component has at most one when expression. |
Use FEL and/or for compound
conditions. |
| String-only params | Custom component params values MUST be strings. |
No objects, arrays, numbers, or booleans as param values. |
| No param interpolation in type names | {param} MUST NOT appear in the component
property. |
Prevents dynamic dispatch. |
| Flat token map | Tokens are a single-level key-value map. | No nested token groups or computed tokens. |
| Editable uniqueness | At most one editable Input per item key (§4.3). | Prevents conflicting write paths. |
| Static tree | The component tree structure is fixed at authoring time. | when hides/shows but does not add/remove nodes. |
Vendor-specific or experimental features MAY be introduced using the
x- prefix convention:
Custom component names: Names starting with
x- (e.g., x-MapPicker,
x-SignaturePad) MAY be used in the components
registry. Conforming processors MUST NOT assign built-in semantics to
x- prefixed names.
Extension properties: Top-level properties
starting with x- are reserved for extensions. Processors
MUST ignore unrecognized x- properties.
Custom style keys: Style object keys starting
with x- are vendor-specific. Processors MUST ignore
unrecognized x- style keys.
Custom token prefixes: Token keys starting with
x- follow the same convention as theme-spec §3.5.
Extension features MUST NOT be required for correct rendering of Core
or Progressive components. An x- feature that is absent or
unsupported MUST NOT cause a processing failure.
This appendix is informative.
The following Component Document defines a multi-page layout for a
budget submission form. It targets a Definition with items for project
information, budget line items, and approval. Wizard-style navigation is
controlled by formPresentation.pageMode in the Definition,
not by the component tree structure.
{
"$formspecComponent": "1.0",
"url": "https://agency.gov/forms/budget-2025/components/wizard",
"version": "1.0.0",
"name": "budget-wizard",
"title": "Budget Form — Multi-Page Layout",
"description": "A three-step wizard-style layout for the annual budget submission form.",
"targetDefinition": {
"url": "https://agency.gov/forms/budget-2025",
"compatibleVersions": ">=1.0.0 <2.0.0"
},
"breakpoints": {
"sm": 576,
"md": 768,
"lg": 1024
},
"tokens": {
"color.primary": "#0057B7",
"color.error": "#D32F2F",
"color.surface": "#FFFFFF",
"color.success": "#2E7D32",
"spacing.sm": "8px",
"spacing.md": "16px",
"spacing.lg": "24px",
"border.radius": "6px",
"typography.body.family": "Inter, system-ui, sans-serif"
},
"components": {
"AddressBlock": {
"params": ["prefix", "title"],
"tree": {
"component": "Card",
"title": "{title}",
"children": [
{ "component": "TextInput", "bind": "{prefix}Street",
"placeholder": "Street address" },
{
"component": "Grid",
"columns": 3,
"gap": "$token.spacing.md",
"responsive": {
"sm": { "columns": 1 }
},
"children": [
{ "component": "TextInput", "bind": "{prefix}City" },
{ "component": "TextInput", "bind": "{prefix}State" },
{ "component": "TextInput", "bind": "{prefix}Zip" }
]
}
]
}
}
},
"tree": {
"component": "Stack",
"children": [
{
"component": "Page",
"title": "Project Information",
"description": "Enter basic details about your project.",
"children": [
{
"component": "Grid",
"columns": 2,
"gap": "$token.spacing.md",
"responsive": {
"sm": { "columns": 1 }
},
"children": [
{ "component": "TextInput", "bind": "projectName" },
{ "component": "TextInput", "bind": "projectCode" }
]
},
{
"component": "Grid",
"columns": 2,
"gap": "$token.spacing.md",
"responsive": {
"sm": { "columns": 1 }
},
"children": [
{ "component": "Select", "bind": "department",
"searchable": true },
{ "component": "Select", "bind": "fiscalYear" }
]
},
{ "component": "TextInput", "bind": "description",
"maxLines": 4, "placeholder": "Describe the project scope" },
{
"component": "AddressBlock",
"params": { "prefix": "project", "title": "Project Location" }
}
]
},
{
"component": "Page",
"title": "Budget Details",
"description": "Add line items and set the total budget.",
"children": [
{
"component": "DataTable",
"bind": "lineItems",
"columns": [
{ "header": "Description", "bind": "itemDescription" },
{ "header": "Category", "bind": "itemCategory" },
{ "header": "Amount", "bind": "itemAmount" }
]
},
{ "component": "Divider" },
{
"component": "Grid",
"columns": 2,
"gap": "$token.spacing.md",
"responsive": {
"sm": { "columns": 1 }
},
"children": [
{
"component": "MoneyInput",
"bind": "totalBudget",
"currency": "USD",
"style": {
"background": "#F0F6FF",
"borderColor": "$token.color.primary",
"borderWidth": "2px"
}
},
{
"component": "MoneyInput",
"bind": "contingency",
"currency": "USD"
}
]
},
{
"component": "Alert",
"severity": "info",
"text": "Total budget is automatically calculated from line items.",
"when": "$totalBudget > 0"
}
]
},
{
"component": "Page",
"title": "Review & Submit",
"description": "Review your submission before signing.",
"children": [
{
"component": "Summary",
"items": [
{ "label": "Project Name", "bind": "projectName" },
{ "label": "Department", "bind": "department" },
{ "label": "Fiscal Year", "bind": "fiscalYear" },
{ "label": "Total Budget", "bind": "totalBudget" },
{ "label": "Contingency", "bind": "contingency" }
]
},
{ "component": "Divider", "label": "Certification" },
{
"component": "Toggle",
"bind": "certify",
"onLabel": "I certify this information is correct",
"offLabel": "Not yet certified"
},
{
"component": "ConditionalGroup",
"when": "$certify = true",
"fallback": "Please certify the information above to proceed.",
"children": [
{
"component": "Signature",
"bind": "approverSignature",
"strokeColor": "#000",
"height": 150
}
]
}
]
}
]
}
}This example demonstrates:
formPresentation.pageMode).AddressBlock) for
reusable address entry.lineItems).when on Alert
and ConditionalGroup).This appendix is normative.
The following table lists all 33 built-in components with their classification and key characteristics.
| # | Component | Category | Level | Children | Bind | Description |
|---|---|---|---|---|---|---|
| 1 | Page | Layout | Core | Yes | Forbidden | Top-level page/section container. |
| 2 | Stack | Layout | Core | Yes | Forbidden | Flexbox vertical/horizontal stacking. |
| 3 | Grid | Layout | Core | Yes | Forbidden | Multi-column grid layout. |
| 4 | Spacer | Layout | Core | No | Forbidden | Empty spacing element. |
| 5 | TextInput | Input | Core | No | Required | Single/multi-line text input. |
| 6 | NumberInput | Input | Core | No | Required | Numeric input with stepper. |
| 7 | DatePicker | Input | Core | No | Required | Date/time/datetime picker. |
| 8 | Select | Input | Core | No | Required | Dropdown single-select. |
| 9 | CheckboxGroup | Input | Core | No | Required | Multi-select checkboxes. |
| 10 | Toggle | Input | Core | No | Required | Boolean switch. |
| 11 | FileUpload | Input | Core | No | Required | File attachment upload. |
| 12 | Heading | Display | Core | No | Forbidden | Section heading (h1–h6). |
| 13 | Text | Display | Core | No | Optional | Static or data-bound text. |
| 14 | Divider | Display | Core | No | Forbidden | Horizontal rule separator. |
| 15 | Card | Container | Core | Yes | Forbidden | Bordered surface grouping. |
| 16 | Collapsible | Container | Core | Yes | Forbidden | Expandable/collapsible section. |
| 17 | ConditionalGroup | Container | Core | Yes | Forbidden | Condition-based visibility group. |
| 18 | Columns | Layout | Progressive | Yes | Forbidden | Explicit column widths layout. |
| 19 | Tabs | Layout | Progressive | Yes | Forbidden | Tabbed navigation container. |
| 20 | Accordion | Layout | Progressive | Yes | Forbidden | Collapsible section list. |
| 21 | RadioGroup | Input | Progressive | No | Required | Radio button single-select. |
| 22 | MoneyInput | Input | Progressive | No | Required | Currency-aware numeric input. |
| 23 | Slider | Input | Progressive | No | Required | Range slider control. |
| 24 | Rating | Input | Progressive | No | Required | Star/icon rating control. |
| 25 | Signature | Input | Progressive | No | Required | Drawn signature capture. |
| 26 | Alert | Display | Progressive | No | Forbidden | Status message banner. |
| 27 | Badge | Display | Progressive | No | Forbidden | Compact label badge. |
| 28 | ProgressBar | Display | Progressive | No | Optional | Visual progress indicator. |
| 29 | Summary | Display | Progressive | No | Forbidden | Key-value summary display. |
| 30 | DataTable | Display | Progressive | No | Optional² | Tabular repeatable data. |
| 31 | Panel | Container | Progressive | Yes | Forbidden | Side panel. |
| 32 | Modal | Container | Progressive | Yes | Forbidden | Dialog overlay. |
| 33 | Popover | Container | Progressive | Yes | Forbidden | Anchored contextual overlay. |
² DataTable binds to a repeatable group key, not a field key.
This appendix is normative.
The following matrix shows which Input components are compatible with
each Definition dataType. A ✓ indicates compatibility.
Components marked (P) are Progressive; all others are Core.
| dataType | TextInput | NumberInput | DatePicker | Select | CheckboxGroup | Toggle | FileUpload | RadioGroup (P) | MoneyInput (P) | Slider (P) | Rating (P) | Signature (P) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
string |
✓ | |||||||||||
number |
✓ | ✓ | ||||||||||
integer |
✓ | ✓ | ✓ | |||||||||
boolean |
✓ | |||||||||||
date |
✓ | |||||||||||
dateTime |
✓ | |||||||||||
time |
✓ | |||||||||||
choice |
✓ | ✓ | ||||||||||
multiChoice |
✓ | |||||||||||
attachment |
✓ | ✓ |
Notes:
bind mode. They are omitted from this matrix because they
do not perform data editing.number
and integer. It adds currency formatting on top of
NumberInput’s capabilities. Authors SHOULD use MoneyInput when the
Definition item has a prefix of "$",
"€", or similar currency indicator.