Components
Create reusable components
Component Definition
Components allow you to create reusable UI pieces. Define components in the components section of your app.
{
"version": "1.0",
"components": {
"Button": {
"params": {
"label": { "type": "string" },
"disabled": { "type": "boolean", "default": false }
},
"view": {
"kind": "element",
"tag": "button",
"props": {
"disabled": { "expr": "param", "name": "disabled" }
},
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "label" } }
]
}
}
}
}Parameters
Components accept parameters using the params field. Each parameter has:
- type: The parameter type (
"string","number","boolean","object","list") - default: Optional default value
{
"params": {
"title": { "type": "string" },
"count": { "type": "number", "default": 0 },
"items": { "type": "list", "default": [] },
"showIcon": { "type": "boolean", "default": true }
}
}Note
Parameters without a default value are required when using the component.
Accessing Parameters
Use the param expression to access parameter values inside a component.
{
"kind": "text",
"value": { "expr": "param", "name": "title" }
}For conditional rendering based on parameters:
{
"kind": "if",
"condition": { "expr": "param", "name": "showIcon" },
"then": {
"kind": "element",
"tag": "span",
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "★" } }
]
}
}Using Components
Use the component kind to instantiate a component.
{
"kind": "component",
"name": "Button",
"props": {
"label": { "expr": "lit", "value": "Click me" },
"disabled": { "expr": "state", "name": "isLoading" }
}
}Props can be:
- Literal values
- State references
- Any expression
Slot for Children
Use slot to allow components to receive children.
Defining a Slot
{
"components": {
"Card": {
"params": {
"title": { "type": "string" }
},
"view": {
"kind": "element",
"tag": "div",
"props": {
"class": { "expr": "lit", "value": "card" }
},
"children": [
{
"kind": "element",
"tag": "h2",
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "title" } }
]
},
{
"kind": "element",
"tag": "div",
"props": {
"class": { "expr": "lit", "value": "card-body" }
},
"children": [
{ "kind": "slot" }
]
}
]
}
}
}
}Using a Component with Children
{
"kind": "component",
"name": "Card",
"props": {
"title": { "expr": "lit", "value": "Welcome" }
},
"children": [
{
"kind": "text",
"value": { "expr": "lit", "value": "This content appears in the slot." }
}
]
}Tip
Slots are powerful for creating layout components like cards, modals, and containers.
Component Local State
Components can have their own independent local state that is not shared between instances.
{
"components": {
"Accordion": {
"params": { "title": { "type": "string" } },
"localState": {
"isExpanded": { "type": "boolean", "initial": false }
},
"localActions": [
{
"name": "toggle",
"steps": [{ "do": "update", "target": "isExpanded", "operation": "toggle" }]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "toggle" } },
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "title" } }
]
},
{
"kind": "if",
"condition": { "expr": "state", "name": "isExpanded" },
"then": { "kind": "slot" }
}
]
}
}
}
}Local State
Define component-scoped state using localState. The syntax is the same as global state:
{
"localState": {
"isOpen": { "type": "boolean", "initial": false },
"count": { "type": "number", "initial": 0 },
"items": { "type": "list", "initial": [] }
}
}Note
Each instance of a component has its own independent local state. Opening one accordion doesn't affect others.
Local Actions
Use localActions to define actions that operate on local state:
{
"localActions": [
{
"name": "increment",
"steps": [{ "do": "update", "target": "count", "operation": "increment" }]
},
{
"name": "reset",
"steps": [{ "do": "set", "target": "count", "value": { "expr": "lit", "value": 0 } }]
}
]
}Warning
Local actions can only use set, update, and setPath steps. Steps like fetch, navigate, and storage are not allowed in local actions.
Accessing Local State
Access local state using the same state expression:
{
"kind": "text",
"value": { "expr": "state", "name": "isExpanded" }
}Within a component, state expressions first check local state, then fall back to global state.
Use Cases
- Accordions: Each section manages its own expanded/collapsed state
- Dropdowns: Independent open/close state per dropdown
- Form fields: Validation state per input
- Toggle buttons: Each button tracks its own on/off state
- Tooltips: Show/hide state per tooltip
Component Composition
Components can use other components.
{
"components": {
"Icon": {
"params": {
"name": { "type": "string" }
},
"view": {
"kind": "element",
"tag": "span",
"props": {
"class": { "expr": "param", "name": "name" }
}
}
},
"IconButton": {
"params": {
"icon": { "type": "string" },
"label": { "type": "string" }
},
"view": {
"kind": "element",
"tag": "button",
"children": [
{
"kind": "component",
"name": "Icon",
"props": {
"name": { "expr": "param", "name": "icon" }
}
},
{
"kind": "text",
"value": { "expr": "param", "name": "label" }
}
]
}
}
}
}Complete Example: Component Library
Here's a complete example with multiple reusable components:
{
"version": "1.0",
"components": {
"Badge": {
"params": {
"text": { "type": "string" },
"variant": { "type": "string", "default": "default" }
},
"view": {
"kind": "element",
"tag": "span",
"props": {
"class": { "expr": "param", "name": "variant" }
},
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "text" } }
]
}
},
"Card": {
"params": {
"title": { "type": "string" }
},
"view": {
"kind": "element",
"tag": "div",
"props": {
"class": { "expr": "lit", "value": "card" }
},
"children": [
{
"kind": "element",
"tag": "div",
"props": {
"class": { "expr": "lit", "value": "card-header" }
},
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "title" } }
]
},
{
"kind": "element",
"tag": "div",
"props": {
"class": { "expr": "lit", "value": "card-content" }
},
"children": [
{ "kind": "slot" }
]
}
]
}
},
"UserCard": {
"params": {
"name": { "type": "string" },
"role": { "type": "string" },
"status": { "type": "string", "default": "active" }
},
"view": {
"kind": "component",
"name": "Card",
"props": {
"title": { "expr": "param", "name": "name" }
},
"children": [
{
"kind": "element",
"tag": "p",
"children": [
{ "kind": "text", "value": { "expr": "param", "name": "role" } }
]
},
{
"kind": "component",
"name": "Badge",
"props": {
"text": { "expr": "param", "name": "status" },
"variant": { "expr": "param", "name": "status" }
}
}
]
}
}
},
"state": {
"users": {
"type": "list",
"initial": [
{ "name": "Alice", "role": "Developer", "status": "active" },
{ "name": "Bob", "role": "Designer", "status": "away" }
]
}
},
"view": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "each",
"items": { "expr": "state", "name": "users" },
"as": "user",
"body": {
"kind": "component",
"name": "UserCard",
"props": {
"name": { "expr": "var", "name": "user", "path": "name" },
"role": { "expr": "var", "name": "user", "path": "role" },
"status": { "expr": "var", "name": "user", "path": "status" }
}
}
}
]
}
}Next Steps
Learn about client-side navigation with Routing.