Advanced UI schema
This page covers dynamic behavior in UI schema: rules for conditional visibility, enabling/disabling and the APPLY effect to patch labels and options at runtime. You’ll also find recipes, troubleshooting, and performance tips.
Rules at a glance
Rules let you react to form data. A rule is attached to any UI schema element via the rule
property.
Supported effects
SHOW
— show when condition is true (hide otherwise)HIDE
— hide when condition is true (show otherwise)ENABLE
— enable when condition is true (disable otherwise)DISABLE
— disable when condition is true (enable otherwise)APPLY
— patch the UI schema element when condition is true (or via multi-case)
{
"type": "Control",
"scope": "#/properties/ssn",
"rule": {
"effect": "SHOW",
"condition": {
"scope": "#/properties/country",
"schema": { "const": "USA" }
}
}
}
Conditions
Conditions define when a rule takes effect. You’ll mostly use schema conditions, but complex flows can combine them.
Schema condition (most common)
Validates a field value against a JSON schema snippet.
{
"scope": "#/properties/country",
"schema": { "const": "USA" }
}
- Use
const
for equality checks (strings, booleans, numbers) - Use
enum
,minimum
,pattern
, etc., for richer checks
Logical conditions (AND / OR)
Combine conditions to express more advanced logic.
{
"type": "AND",
"conditions": [
{ "scope": "#/properties/isVIP", "schema": { "const": true } },
{ "scope": "#/properties/orders", "schema": { "minimum": 10 } }
]
}
{
"type": "OR",
"conditions": [
{ "scope": "#/properties/tier", "schema": { "const": "gold" } },
{ "scope": "#/properties/tier", "schema": { "const": "platinum" } }
]
}
Leaf equality
A shorthand to match the current field value exactly (used inside APPLY cases).
{ "type": "LEAF", "expectedValue": "advanced" }
Function condition (expert)
When needed, provide a custom validator. This gives you full access to data
, fullData
, path
, and the
current uischemaElement
.
{
"validate": "(ctx) => Array.isArray(ctx.fullData.products) && ctx.fullData.products.length > 0"
}
Custom functions are powerful but harder to test and review. Prefer schema and logical conditions first.
APPLY: dynamic patches
APPLY
lets you patch UI schema properties (like label
, options
, or any element property) based on form state.
Single outcome
{
"type": "Control",
"scope": "#/properties/quantity",
"rule": {
"effect": "APPLY",
"condition": { "scope": "#/properties/inStock", "schema": { "const": false } },
"outcome": { "enabled": false, "label": "Quantity (Out of stock)" }
}
}
Multi-case outcomes
Evaluate cases in order; the first match wins. Provide a final catch-all outcome without condition
.
{
"type": "Control",
"scope": "#/properties/field",
"rule": {
"effect": "APPLY",
"cases": [
{
"condition": { "scope": "#/properties/mode", "schema": { "const": "basic" } },
"outcome": { "label": "Basic field", "options": { "multi": false } }
},
{
"condition": { "type": "LEAF", "expectedValue": "advanced" },
"outcome": { "label": "Advanced field", "options": { "multi": true } }
},
{
"outcome": { "label": "Default field" }
}
]
}
}
Dynamic options (unset/override)
{
"type": "Control",
"scope": "#/properties/password",
"options": { "format": "password" },
"rule": {
"effect": "APPLY",
"condition": { "scope": "#/properties/showPassword", "schema": { "const": true } },
"outcome": { "options": { "format": null } }
}
}
Use outcome.options
to tweak how a control renders without duplicating the element.
Common dynamic patterns
Show a section when a toggle is ON
{
"type": "Group",
"label": "Delivery settings",
"rule": { "effect": "SHOW", "condition": { "scope": "#/properties/delivery", "schema": { "const": true } } },
"elements": [
{ "type": "Control", "scope": "#/properties/address" },
{ "type": "Control", "scope": "#/properties/window" }
]
}
Enable a field only for admins
{
"type": "Control",
"scope": "#/properties/limit",
"rule": { "effect": "ENABLE", "condition": { "scope": "#/properties/isAdmin", "schema": { "const": true } } }
}
Change label based on selection
{
"type": "Control",
"scope": "#/properties/name",
"rule": {
"effect": "APPLY",
"cases": [
{ "condition": { "scope": "#/properties/type", "schema": { "const": "person" } }, "outcome": { "label": "Full name" } },
{ "condition": { "scope": "#/properties/type", "schema": { "const": "company" } }, "outcome": { "label": "Company name" } },
{ "outcome": { "label": "Name" } }
]
}
}
Derive options based on other data
{
"type": "Control",
"scope": "#/properties/region",
"rule": {
"effect": "APPLY",
"condition": { "scope": "#/properties/country", "schema": { "enum": ["US", "CA"] } },
"outcome": { "options": { "placeholder": "Pick a region (US/CA)" } }
}
}
Stepper with nav buttons
{
"type": "Categorization",
"options": { "variant": "stepper", "showNavButtons": true },
"elements": [
{ "type": "Category", "label": "General", "elements": [ { "type": "Control", "scope": "#/properties/title" } ] },
{ "type": "Category", "label": "Details", "elements": [ { "type": "Control", "scope": "#/properties/description" } ] }
]
}
Arrays: show sort buttons only when > 1 item
{
"type": "Control",
"scope": "#/properties/tasks",
"options": { "showArrayLayoutSortButtons": false },
"rule": {
"effect": "APPLY",
"condition": { "validate": "(ctx) => Array.isArray(ctx.fullData.tasks) && ctx.fullData.tasks.length > 1" },
"outcome": { "options": { "showArrayLayoutSortButtons": true } }
}
}
Troubleshooting
My rule doesn’t trigger
- ✅ Check the
scope
pointer — it must match the JSON Schema path (e.g.,#/properties/featureFlag
) - ✅ Validate the condition schema — prefer
const
for equality checks - ✅ Ensure the data type matches (e.g.,
"true"
vstrue
) - ✅ If using arrays/objects, remember item contexts (e.g.,
#/items/properties/name
inside array detail)
The control is hidden even when it should show
- If using
SHOW
, it hides when the condition is false. Switch toHIDE
if you want the inverse behavior. - Check for multiple rules affecting the same element (e.g., parent
Group
hidden).
APPLY didn’t change my option
- Confirm the outcome shape:
{ "outcome": { "options": { ... } } }
- To unset an option, set it to
null
(e.g.,"format": null
). - Remember that first matching case wins — ensure case order is correct.
Reference: rule schemas (for copy/paste)
// Rule with a single condition
{
effect: "SHOW" | "HIDE" | "ENABLE" | "DISABLE" | "APPLY",
condition: SchemaCondition | LogicalCondition | LeafCondition | FunctionCondition,
outcome?: ApplyOutcome
}
// Multi-case APPLY
{
effect: "APPLY",
cases: Array<{
condition?: SchemaCondition | LogicalCondition | LeafCondition | FunctionCondition,
outcome: ApplyOutcome
}>
}
// Condition shapes
type SchemaCondition = { scope: string; schema: object; failWhenUndefined?: boolean };
type LogicalCondition = { type: "AND" | "OR"; conditions: Array<Condition> };
type LeafCondition = { type: "LEAF"; expectedValue: any };
type FunctionCondition = { validate: string /* JS source as string */ };
// Outcome patches any UI schema element fields
type ApplyOutcome = { [key: string]: any };