Components

Create reusable components

Component Definition

Components allow you to create reusable UI pieces. Define components in the components section of your app.

json
{
  "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
json
{
  "params": {
    "title": { "type": "string" },
    "count": { "type": "number", "default": 0 },
    "items": { "type": "list", "default": [] },
    "showIcon": { "type": "boolean", "default": true }
  }
}

Accessing Parameters

Use the param expression to access parameter values inside a component.

json
{
  "kind": "text",
  "value": { "expr": "param", "name": "title" }
}

For conditional rendering based on parameters:

json
{
  "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.

json
{
  "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

json
{
  "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

json
{
  "kind": "component",
  "name": "Card",
  "props": {
    "title": { "expr": "lit", "value": "Welcome" }
  },
  "children": [
    {
      "kind": "text",
      "value": { "expr": "lit", "value": "This content appears in the slot." }
    }
  ]
}

Component Local State

Components can have their own independent local state that is not shared between instances.

json
{
  "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:

json
{
  "localState": {
    "isOpen": { "type": "boolean", "initial": false },
    "count": { "type": "number", "initial": 0 },
    "items": { "type": "list", "initial": [] }
  }
}

Local Actions

Use localActions to define actions that operate on local state:

json
{
  "localActions": [
    {
      "name": "increment",
      "steps": [{ "do": "update", "target": "count", "operation": "increment" }]
    },
    {
      "name": "reset",
      "steps": [{ "do": "set", "target": "count", "value": { "expr": "lit", "value": 0 } }]
    }
  ]
}

Accessing Local State

Access local state using the same state expression:

json
{
  "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.

json
{
  "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:

json
{
  "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.