---
title: Context & Variables
description: What data expressions can access and where it comes from.
icon: database
---

import { ComponentPreview } from '/snippets/component-preview.mdx'

Expressions resolve identifiers from a context object. What's in that context depends on where the expression runs — global state is always available, but `let` bindings and [ForEach](/components/foreach) can layer on additional scoped values.

## Global State

Global state is always accessible from any expression. Values enter global state from three sources:

**`AppResult`** — the initial state set by your server code:
```python
from prefab_ui import AppResult

AppResult(state={"count": 0, "name": "Arthur"}, view=...)
```

**Named form controls** — any component with a `name` prop syncs its current value to state automatically. `Input(name="city")` updates `{{ city }}` on every keystroke.

**Actions** — [SetState](/actions/set-state) and [CallTool](/actions/call-tool) (via `result_key`) write to global state when they fire.

## Local Scope (`let`)

Any container component can introduce scoped bindings via `let` — a dict of names to template expressions. These values are available to all children and shadow outer scope for the same key.

<ComponentPreview json={{"view":{"cssClass":"gap-2","let":{"greeting":"Don't Panic","name":"Arthur"},"type":"Column","children":[{"content":"{{ greeting }}, {{ name }}","type":"Text"},{"let":{"name":"Ford"},"type":"Column","children":[{"content":"{{ greeting }}, {{ name }}","type":"Text"}]}]}}}>
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Column, Text

with Column(gap=2, let={"greeting": "Don't Panic", "name": "Arthur"}):
    Text("{{ greeting }}, {{ name }}")
    with Column(let={"name": "Ford"}):
        Text("{{ greeting }}, {{ name }}")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-2",
    "let": {
      "greeting": "Don't Panic",
      "name": "Arthur"
    },
    "type": "Column",
    "children": [
      {
        "content": "{{ greeting }}, {{ name }}",
        "type": "Text"
      },
      {
        "let": {
          "name": "Ford"
        },
        "type": "Column",
        "children": [
          {
            "content": "{{ greeting }}, {{ name }}",
            "type": "Text"
          }
        ]
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

The first `Text` renders "Don't Panic, Arthur". The second renders "Don't Panic, Ford" — the inner `let` shadows `name` but inherits `greeting` from the outer scope.

Unlike `set_initial_state()`, `let` bindings are scoped and read-only — they exist only inside the container, and user interactions can't change them. Use `set_initial_state()` for anything interactive (form inputs, toggles, lists the user edits). Use `let` when you're passing fixed data into a section of the UI.

On [ForEach](/components/foreach), `let` is especially useful for capturing the loop index before it gets shadowed by a nested loop:

```python
with ForEach("groups", let={"gi": "{{ $index }}"}):
    # gi holds the outer index; $index inside a nested ForEach refers to the inner loop
    with ForEach("groups.{{ gi }}.todos"):
        Text("Group {{ gi }}, item {{ $index }}")
```

## Dot Paths

Use dot notation to reach into nested objects:

```
{{ user.address.city }}
{{ order.items.length }}
```

`.length` works on both arrays and strings. If any segment of the path is null or undefined, the entire expression resolves to undefined.

## Special Variables

Several variables are injected automatically by the framework in specific contexts.

### `$event`

Available inside action specs. Contains the value from the triggering interaction — what that means depends on the component:

| Component | `$event` value |
|-----------|---------------|
| Input / Textarea | Current text (string) |
| Slider | Current position (number) |
| Checkbox / Switch | Checked state (boolean) |
| Select | Selected value (string) |
| RadioGroup | Selected value (string) |
| Button | `undefined` |

[SetState](/actions/set-state) defaults to `"{{ $event }}"` when no explicit value is provided, which is why `SetState("volume")` on a slider captures the slider position.

### `$error`

Available inside `on_error` callbacks. Contains the error message string from the failed action.

```python
from prefab_ui.actions import CallTool, ShowToast

Button(
    "Save",
    on_click=CallTool(
        "save_data",
        on_error=ShowToast("Failed: {{ $error }}", variant="error"),
    ),
)
```

### `$index`

Available inside [ForEach](/components/foreach) iterations. The zero-based index of the current item.

```python
from prefab_ui.components import ForEach, Text

with ForEach("items"):
    Text("{{ $index + 1 }}. {{ name }}")
    # Renders: "1. First", "2. Second", etc.
```

`$index` is how actions target specific list items from inside a loop — `SetState("todos.{{ $index }}.done")` updates the `done` field of the item at the current position.

### `$item`

Available inside [ForEach](/components/foreach) iterations. A reference to the entire current item object. Individual fields are also available directly (`{{ name }}` instead of `{{ $item.name }}`), but `$item` is useful when you need to pass the whole object to an action:

```python
from prefab_ui.components import ForEach, Button, Text
from prefab_ui.actions import CallTool

with ForEach("users"):
    Text("{{ name }}")
    Button(
        "Edit",
        on_click=CallTool(
            "edit_user",
            arguments={"user": "{{ $item }}"},
        ),
    )
```

## Conditional Rendering

[If/Elif/Else](/components/conditional) blocks use expressions to control which components render. The expression evaluates to a truthy or falsy value, and the first matching branch renders. This is one of the most practical uses of expression context — combining state from different sources to determine what the user sees:

<ComponentPreview json={{"view":{"cssClass":"gap-3","type":"Column","children":[{"content":"Inventory: {{ inventory }}","type":"Text"},{"type":"Slider","min":0.0,"max":30.0,"step":1.0,"name":"inventory","disabled":false},{"type":"Condition","cases":[{"when":"{{ inventory == 0 }}","children":[{"type":"Alert","variant":"destructive","children":[{"type":"AlertDescription","content":"Out of stock!"}]}]},{"when":"{{ inventory > 0 && inventory < 10 }}","children":[{"type":"Alert","variant":"default","children":[{"type":"AlertDescription","content":"Low stock warning"}]}]}]}]},"state":{"inventory":15}}}>
<CodeGroup>
```python Python icon="python"
from prefab_ui.app import set_initial_state
from prefab_ui.components import (
    Alert,
    AlertDescription,
    Column,
    Elif,
    If,
    Slider,
    Text,
)
set_initial_state(inventory=15)

with Column(gap=3):
    Text("Inventory: {{ inventory }}")
    Slider(
        name="inventory",
        min=0,
        max=30,
        step=1,
    )
    with If("{{ inventory == 0 }}"):
        with Alert(variant="destructive"):
            AlertDescription("Out of stock!")
    with Elif("{{ inventory > 0 && inventory < 10 }}"):
        with Alert():
            AlertDescription("Low stock warning")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-3",
    "type": "Column",
    "children": [
      {
        "content": "Inventory: {{ inventory }}",
        "type": "Text"
      },
      {
        "type": "Slider",
        "min": 0.0,
        "max": 30.0,
        "step": 1.0,
        "name": "inventory",
        "disabled": false
      },
      {
        "type": "Condition",
        "cases": [
          {
            "when": "{{ inventory == 0 }}",
            "children": [
              {
                "type": "Alert",
                "variant": "destructive",
                "children": [
                  {
                    "type": "AlertDescription",
                    "content": "Out of stock!"
                  }
                ]
              }
            ]
          },
          {
            "when": "{{ inventory > 0 && inventory < 10 }}",
            "children": [
              {
                "type": "Alert",
                "variant": "default",
                "children": [
                  {
                    "type": "AlertDescription",
                    "content": "Low stock warning"
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "state": {
    "inventory": 15
  }
}
```
</CodeGroup>
</ComponentPreview>

Drag the slider to zero to see the "Out of stock!" alert appear. Values between 1 and 9 show the "Low stock warning" instead.
