> ## Documentation Index
> Fetch the complete documentation index at: https://prefab.prefect.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# State

> Prefab's centralized client-side data store and the reactive bindings that keep components in sync.

export const ComponentPreview = ({json, height, resizable, bare, hideJson, playground, children}) => {
  const hostRef = React.useRef(null);
  const handleRef = React.useRef(null);
  const cardRef = React.useRef(null);
  const containerRef = React.useRef(null);
  const [previewWidth, setPreviewWidth] = React.useState(null);
  const [isDragging, setIsDragging] = React.useState(false);
  const [playgroundHref, setPlaygroundHref] = React.useState(null);
  React.useEffect(function () {
    if (!playground) return;
    var isLocal = location.hostname === "localhost" || location.hostname === "127.0.0.1";
    var path = isLocal ? "/playground" : "/docs/playground";
    var url = new URL(path, window.location.href);
    url.hash = "code=" + playground;
    setPlaygroundHref(url.href);
  }, [playground]);
  var jsonStr = typeof json === "string" ? json : JSON.stringify(json, null, 2);
  React.useEffect(function () {
    if (cardRef.current) {
      var card = cardRef.current.closest(".group");
      if (card) card.classList.remove("group");
    }
    var host = hostRef.current;
    if (!host || !json) return;
    function mount() {
      if (!window.__prefab || !host) return;
      var dark = document.documentElement.classList.contains("dark");
      handleRef.current = window.__prefab.mountPreview(host, jsonStr, {
        dark: dark
      });
      if (bare && host.shadowRoot) {
        var m = host.shadowRoot.querySelector("[data-prefab-mount]");
        if (m) m.style.background = "transparent";
      }
    }
    if (window.__prefab) {
      mount();
    } else {
      if (!window.__prefabLoading) {
        if (!window.__prefabReady) {
          var s = document.createElement("script");
          s.src = "/renderer.js";
          document.head.appendChild(s);
        }
        window.__prefabLoading = new Promise(function (resolve) {
          function check() {
            if (window.__prefabReady) {
              window.__prefabReady.then(resolve);
            } else {
              setTimeout(check, 10);
            }
          }
          check();
        });
      }
      window.__prefabLoading.then(mount);
    }
    var observer = new MutationObserver(function () {
      var dark = document.documentElement.classList.contains("dark");
      if (handleRef.current) handleRef.current.setDark(dark);
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return function () {
      observer.disconnect();
      if (handleRef.current) {
        handleRef.current.unmount();
        handleRef.current = null;
      }
    };
  }, [json]);
  function startResize(e) {
    e.preventDefault();
    var startX = e.clientX;
    var startW = hostRef.current ? hostRef.current.offsetWidth : 0;
    var maxW = containerRef.current ? containerRef.current.offsetWidth - 10 : startW;
    setIsDragging(true);
    function onMove(ev) {
      var w = Math.max(260, Math.min(startW + (ev.clientX - startX), maxW));
      setPreviewWidth(w);
    }
    function onUp() {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      setIsDragging(false);
    }
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  }
  if (bare) {
    return <div ref={hostRef} />;
  }
  if (resizable) {
    return <Card>
        <div ref={cardRef} />
        <div ref={containerRef} style={{
      display: "flex",
      position: "relative",
      overflow: "hidden"
    }}>
          <div ref={hostRef} style={{
      flex: previewWidth ? "none" : 1,
      width: previewWidth ? previewWidth + "px" : undefined,
      minWidth: 0
    }} />
          <div onMouseDown={startResize} style={{
      width: "14px",
      flexShrink: 0,
      cursor: "col-resize",
      background: isDragging ? "var(--border, #e5e7eb)" : "color-mix(in srgb, var(--border, #e5e7eb) 40%, transparent)",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      transition: "background 0.15s",
      userSelect: "none"
    }}>
            <svg width="6" height="24" style={{
      opacity: 0.5
    }}>
              <circle cx="2" cy="4" r="1.2" fill="currentColor" />
              <circle cx="2" cy="9" r="1.2" fill="currentColor" />
              <circle cx="2" cy="14" r="1.2" fill="currentColor" />
              <circle cx="2" cy="19" r="1.2" fill="currentColor" />
              <circle cx="5" cy="4" r="1.2" fill="currentColor" />
              <circle cx="5" cy="9" r="1.2" fill="currentColor" />
              <circle cx="5" cy="14" r="1.2" fill="currentColor" />
              <circle cx="5" cy="19" r="1.2" fill="currentColor" />
            </svg>
          </div>
          <div style={{
      flex: previewWidth ? 1 : "0 0 24px",
      minWidth: "24px",
      background: "repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--border, #e5e7eb) 3px, var(--border, #e5e7eb) 4px)",
      opacity: 0.4
    }} />
          {previewWidth && <div style={{
      position: "absolute",
      bottom: "8px",
      right: "8px",
      background: "var(--background, white)",
      border: "1px solid var(--border, #e5e7eb)",
      borderRadius: "4px",
      padding: "1px 6px",
      fontSize: "11px",
      fontFamily: "monospace",
      color: "var(--muted-foreground, #6b7280)",
      pointerEvents: "none"
    }}>
              {previewWidth}px
            </div>}
        </div>
        {playgroundHref && <div style={{
      display: "flex",
      justifyContent: "flex-end",
      padding: "4px 0 8px"
    }}>
            <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
      fontSize: "11px",
      opacity: 0.4,
      textDecoration: "none",
      textDecorationLine: "none",
      borderBottom: "none",
      boxShadow: "none",
      display: "inline-flex",
      alignItems: "center",
      gap: "3px",
      transition: "opacity 0.15s",
      fontWeight: 400
    }} onMouseOver={function (e) {
      e.currentTarget.style.opacity = "0.6";
    }} onMouseOut={function (e) {
      e.currentTarget.style.opacity = "0.4";
    }}>
              Edit in Playground
              <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
            </a>
          </div>}
        <div style={{
      marginBottom: "-2rem"
    }}>{children}</div>
      </Card>;
  }
  return <Card>
      <div ref={cardRef} />
      <div ref={hostRef} />
      {playgroundHref && <div style={{
    display: "flex",
    justifyContent: "flex-end",
    padding: "4px 0 8px"
  }}>
          <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
    fontSize: "11px",
    opacity: 0.4,
    textDecoration: "none",
    textDecorationLine: "none",
    borderBottom: "none",
    boxShadow: "none",
    display: "inline-flex",
    alignItems: "center",
    gap: "3px",
    transition: "opacity 0.15s",
    fontWeight: 400
  }} onMouseOver={function (e) {
    e.currentTarget.style.opacity = "0.6";
  }} onMouseOut={function (e) {
    e.currentTarget.style.opacity = "0.4";
  }}>
            Edit in Playground
            <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
          </a>
        </div>}
      <div style={{
    marginBottom: "-2rem"
  }}>{children}</div>
    </Card>;
};

State is the renderer's memory. It's a flat key-value store that lives in the browser for the lifetime of your app, visible to every component, writable from every interaction. When state changes, every component that reads from it updates automatically.

Because state is centralized and global, components don't own their data. There's no local component state, no `useState`, no callbacks threading data up through a tree. Components declare which keys they depend on, and changes propagate everywhere automatically. Any component anywhere in the tree can read or write any state key.

## Providing initial values

State starts empty unless you seed it. Pass initial state to `PrefabApp` via the `state` parameter:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.app import PrefabApp

app = PrefabApp(view=view, state={"count": 0, "title": "Dashboard", "items": []})
```

You can also use `Rx` to create reactive references to state keys:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.rx import Rx

count = Rx("count")         # reactive reference to {{ count }}
items = Rx("items")         # reactive reference to {{ items }}
```

Components with a `value` prop seed their state key too. `Input(name="city", value="London")` registers `city` with an initial value of `"London"`.

Either way, those keys are immediately available to every expression in your component tree.

## Interactive components as state sources

Interactive components with a `name` prop are the most natural state source. They automatically sync their current value to that key on every interaction.

<ComponentPreview json={{"view":{"cssClass":"gap-3","type":"Column","children":[{"name":"name_input","type":"Input","inputType":"text","placeholder":"Type your name..","disabled":false,"readOnly":false,"required":false},{"content":"Hello, {{ name_input | 'stranger' }}!","type":"Text"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBJbnB1dCwgVGV4dAoKd2l0aCBDb2x1bW4oZ2FwPTMpOgogICAgbmFtZV9pbnB1dCA9IElucHV0KG5hbWU9Im5hbWVfaW5wdXQiLCBwbGFjZWhvbGRlcj0iVHlwZSB5b3VyIG5hbWUuLiIpCiAgICBUZXh0KCJIZWxsbywge3sgbmFtZV9pbnB1dCB8ICdzdHJhbmdlcicgfX0hIikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Column, Input, Text

    with Column(gap=3):
        name_input = Input(name="name_input", placeholder="Type your name..")
        Text("Hello, {{ name_input | 'stranger' }}!")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-3",
        "type": "Column",
        "children": [
          {
            "name": "name_input",
            "type": "Input",
            "inputType": "text",
            "placeholder": "Type your name..",
            "disabled": false,
            "readOnly": false,
            "required": false
          },
          {"content": "Hello, {{ name_input | 'stranger' }}!", "type": "Text"}
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

As you type in the input, the greeting updates on every keystroke. This is a gentle introduction to Prefab's expression language: double curly braces reference state values, and the pipe operator provides a fallback (`"stranger"`) when the key is undefined.

Every interactive control works this way. A `Slider(name="volume")` writes its position on every drag. A `Checkbox(name="agree")` writes `true`/`false`. A `Select(name="size")` writes the selected option value. Whatever name you give becomes a key that expressions and actions can reference.

## Reading state

Inside component props, `{{ key }}` template expressions resolve to the current value at render time. **Any** string prop accepts these expressions, and the renderer re-evaluates them whenever a referenced key changes. `Text("{{ count }}")` displays the number; `Progress(value="{{ volume }}")` drives the bar.

The expression language itself supports operators and formatting: `{{ count + 10 }}` adds ten, `{{ name | upper }}` uppercases. You can write these directly in any string prop.

In Python, `Rx` objects compile to the same `{{ key }}` expressions in the protocol output, but in your code they behave like Python values — you can apply operators, use them in f-strings, and combine them with other values:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.rx import Rx

count = Rx("count")

Text(count)                            # {{ count }}
Text(f"Count: {count}")                # Count: {{ count }}
Text(count + 10)                       # {{ count + 10 }}
Text(f"Items: {count} total")          # Items: {{ count }} total
```

The `STATE` constant works identically but doesn't require capturing the return value — useful when state is set by actions rather than initialization, or when you want a quick reference without creating an `Rx` first:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import STATE

Text(STATE.count)                            # {{ count }}
```

The full Rx system, including operators, formatting, and the `.rx` shorthand, is covered in [Expressions](/docs/concepts/expressions).

## Nested state and dot paths

State values can be nested objects, and you reach into them with dot notation. `{{ profile.name }}` reads the `name` field of the `profile` object. The `name` prop accepts dot paths too, so inputs can bind directly to nested fields:

<ComponentPreview json={{"view":{"cssClass":"pf-app-root","type":"Div","children":[{"cssClass":"gap-3","type":"Column","children":[{"type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"{{ profile.name }}"},{"type":"CardDescription","content":"{{ profile.title }}"}]}]},{"type":"Field","invalid":false,"disabled":false,"children":[{"type":"Label","text":"Name","optional":false},{"name":"profile.name","type":"Input","inputType":"text","placeholder":"Name","disabled":false,"readOnly":false,"required":false}]},{"type":"Field","invalid":false,"disabled":false,"children":[{"type":"Label","text":"Title","optional":false},{"name":"profile.title","type":"Input","inputType":"text","placeholder":"Title","disabled":false,"readOnly":false,"required":false}]}]}]},"state":{"profile":{"name":"Arthur Dent","title":"Sandwich Maker, Lamuella"}}}} playground="ZnJvbSBwcmVmYWJfdWkuYXBwIGltcG9ydCBQcmVmYWJBcHAKZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgKAogICAgQ2FyZCwgQ2FyZERlc2NyaXB0aW9uLCBDYXJkSGVhZGVyLCBDYXJkVGl0bGUsCiAgICBDb2x1bW4sIEZpZWxkLCBJbnB1dCwgTGFiZWwsCikKCndpdGggQ29sdW1uKGdhcD0zKSBhcyB2aWV3OgogICAgd2l0aCBDYXJkKCk6CiAgICAgICAgd2l0aCBDYXJkSGVhZGVyKCk6CiAgICAgICAgICAgIENhcmRUaXRsZSgie3sgcHJvZmlsZS5uYW1lIH19IikKICAgICAgICAgICAgQ2FyZERlc2NyaXB0aW9uKCJ7eyBwcm9maWxlLnRpdGxlIH19IikKICAgIHdpdGggRmllbGQoKToKICAgICAgICBMYWJlbCgiTmFtZSIpCiAgICAgICAgSW5wdXQobmFtZT0icHJvZmlsZS5uYW1lIiwgcGxhY2Vob2xkZXI9Ik5hbWUiKQogICAgd2l0aCBGaWVsZCgpOgogICAgICAgIExhYmVsKCJUaXRsZSIpCiAgICAgICAgSW5wdXQobmFtZT0icHJvZmlsZS50aXRsZSIsIHBsYWNlaG9sZGVyPSJUaXRsZSIpCgphcHAgPSBQcmVmYWJBcHAoCiAgICBzdGF0ZT17CiAgICAgICAgInByb2ZpbGUiOiB7CiAgICAgICAgICAgICJuYW1lIjogIkFydGh1ciBEZW50IiwKICAgICAgICAgICAgInRpdGxlIjogIlNhbmR3aWNoIE1ha2VyLCBMYW11ZWxsYSIsCiAgICAgICAgfSwKICAgIH0sCiAgICB2aWV3PXZpZXcsCikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.app import PrefabApp
    from prefab_ui.components import (
        Card, CardDescription, CardHeader, CardTitle,
        Column, Field, Input, Label,
    )

    with Column(gap=3) as view:
        with Card():
            with CardHeader():
                CardTitle("{{ profile.name }}")
                CardDescription("{{ profile.title }}")
        with Field():
            Label("Name")
            Input(name="profile.name", placeholder="Name")
        with Field():
            Label("Title")
            Input(name="profile.title", placeholder="Title")

    app = PrefabApp(
        state={
            "profile": {
                "name": "Arthur Dent",
                "title": "Sandwich Maker, Lamuella",
            },
        },
        view=view,
    )
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "pf-app-root",
        "type": "Div",
        "children": [
          {
            "cssClass": "gap-3",
            "type": "Column",
            "children": [
              {
                "type": "Card",
                "children": [
                  {
                    "type": "CardHeader",
                    "children": [
                      {"type": "CardTitle", "content": "{{ profile.name }}"},
                      {"type": "CardDescription", "content": "{{ profile.title }}"}
                    ]
                  }
                ]
              },
              {
                "type": "Field",
                "invalid": false,
                "disabled": false,
                "children": [
                  {"type": "Label", "text": "Name", "optional": false},
                  {
                    "name": "profile.name",
                    "type": "Input",
                    "inputType": "text",
                    "placeholder": "Name",
                    "disabled": false,
                    "readOnly": false,
                    "required": false
                  }
                ]
              },
              {
                "type": "Field",
                "invalid": false,
                "disabled": false,
                "children": [
                  {"type": "Label", "text": "Title", "optional": false},
                  {
                    "name": "profile.title",
                    "type": "Input",
                    "inputType": "text",
                    "placeholder": "Title",
                    "disabled": false,
                    "readOnly": false,
                    "required": false
                  }
                ]
              }
            ]
          }
        ]
      },
      "state": {"profile": {"name": "Arthur Dent", "title": "Sandwich Maker, Lamuella"}}
    }
    ```
  </CodeGroup>
</ComponentPreview>

Integer segments address array items. `{{ todos.0.done }}` reads the `done` field on the first item in `todos`. Inside a `ForEach` loop, combine this with `{{ $index }}` to target whichever row the user is interacting with: `SetState("todos.{{ $index }}.done", True)` checks off the current item.

If any segment along a path is missing or the wrong type, the expression resolves to undefined rather than throwing. Reads return undefined gracefully; writes are no-ops with a console warning.

## Writing to state

Two things write to state: interactive controls (automatically, via the `name` prop) and [actions](/docs/concepts/actions) (explicitly, in response to events). `SetState` assigns a value. `ToggleState` flips a boolean. `AppendState` and `PopState` manipulate arrays. `CallTool` and `Fetch` make their results available as `$result` in `on_success` callbacks, where you can write them to state with `SetState`.

State is deliberately simple: a flat map with dot-path addressing for nesting. When you need derived values, inline expressions like `{{ price * quantity }}` handle them directly. For reuse across multiple components, assign the expression to a Python variable with `Rx` and reference it wherever needed.
