> ## 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.

# Core Concepts

> The four concepts that everything in Prefab is built from.

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>;
};

At its core, Prefab is a protocol: a JSON document that describes a UI as a tree of components, with state, expressions, and actions wired in. A JavaScript renderer reads that document and turns it into a live interface. You never write the JSON by hand. Instead, you use Prefab's Python SDK, which gives you a natural way to build component trees that compile to the protocol.

Everything in Prefab is built from four concepts:

* **Components** define what the user sees
* **State** holds the data
* **Expressions** read state to keep the display current
* **Actions** respond to interactions by changing state or calling your server

They compose into a cycle: components display state through expressions, users trigger actions, actions update state, and expressions re-evaluate to update the display. Understanding each one, and how they connect, is the foundation for building anything in Prefab.

## Components

Components are the building blocks of every interface. `Text` renders content. A `Button` triggers actions. A `Card` wraps other components in a visual container. Components form a tree where parents contain children, and in Python, the `with` statement expresses that nesting naturally:

<ComponentPreview json={{"view":{"cssClass":"w-48","type":"Card","children":[{"type":"CardHeader","children":[{"cssClass":"gap-2","type":"Row","children":[{"type":"CardTitle","content":"API Server"},{"type":"Badge","label":"Healthy","variant":"success"}]}]},{"type":"CardContent","children":[{"content":"Uptime: 99.97%","type":"Text"}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQmFkZ2UsIENhcmQsIENhcmRDb250ZW50LCBDYXJkSGVhZGVyLCBDYXJkVGl0bGUsIFJvdywgVGV4dAoKd2l0aCBDYXJkKGNzc19jbGFzcz0idy00OCIpIGFzIHZpZXc6CiAgICB3aXRoIENhcmRIZWFkZXIoKToKICAgICAgICB3aXRoIFJvdyhnYXA9Mik6CiAgICAgICAgICAgIENhcmRUaXRsZSgiQVBJIFNlcnZlciIpCiAgICAgICAgICAgIEJhZGdlKCJIZWFsdGh5IiwgdmFyaWFudD0ic3VjY2VzcyIpCiAgICB3aXRoIENhcmRDb250ZW50KCk6CiAgICAgICAgVGV4dCgiVXB0aW1lOiA5OS45NyUiKQo">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Badge, Card, CardContent, CardHeader, CardTitle, Row, Text

    with Card(css_class="w-48") as view:
        with CardHeader():
            with Row(gap=2):
                CardTitle("API Server")
                Badge("Healthy", variant="success")
        with CardContent():
            Text("Uptime: 99.97%")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "w-48",
        "type": "Card",
        "children": [
          {
            "type": "CardHeader",
            "children": [
              {
                "cssClass": "gap-2",
                "type": "Row",
                "children": [
                  {"type": "CardTitle", "content": "API Server"},
                  {"type": "Badge", "label": "Healthy", "variant": "success"}
                ]
              }
            ]
          },
          {
            "type": "CardContent",
            "children": [{"content": "Uptime: 99.97%", "type": "Text"}]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

`Row` arranges children horizontally, `Column` stacks them vertically, `Grid` places them in a grid, and they all nest freely. Switch to the Protocol tab to see the JSON that this Python produces; that's what the renderer actually receives. The [Composition](/concepts/components) guide covers layout primitives, cards, grids, loops, and forward references.

## State

State is a key-value store that the renderer maintains for the lifetime of your app. It starts with whatever values you provide and changes as users interact. State is global: every component can read from it and every action can write to it.

Interactive components are the most natural source of state. An `Input(name="city")` writes its current text to the `city` key on every keystroke. A `Slider(name="volume")` writes its position on every drag. Components with a `value` prop also seed their state key automatically: `Input(value="London")` means the key starts with `"London"` before the user types anything.

The [State](/concepts/state) guide covers seeding, form bindings, dot paths for nested data, and how state gets written.

## Expressions

Expressions are how components read from state and compute display values. `{{ count }}` in a text component means "display whatever `count` holds right now, and update whenever it changes." Every `{{ }}` expression declares a live dependency: the renderer tracks which keys it reads, re-evaluates when any change, and updates just those parts of the display. This happens entirely in the browser, instantly.

Beyond reading, expressions can compute: `{{ price * quantity }}` multiplies two keys, `{{ status == 'active' }}` produces a boolean, `{{ revenue | currency }}` formats a number as `$2,847,500.00`. The language is intentionally bounded, covering enough to drive any UI while keeping business logic in your Python code.

In Python, you write expressions using the `Rx` class. `Rx("count")` compiles to `{{ count }}`, and operators chain naturally: `(price * quantity).currency()` compiles to `{{ price * quantity | currency }}`. Stateful components have a `.rx` shorthand; `slider.rx` returns an `Rx` bound to the slider's state key, so you can wire components together without writing key names by hand.

The [Expressions](/concepts/expressions) guide covers the Rx DSL, operators, pipes, and special variables.

## Actions

Actions are what happen when users interact. Click a button, change a slider, submit a form: each interaction can carry an action that fires when the event occurs.

**Client actions** run instantly in the browser: `SetState` assigns a value, `ToggleState` flips a boolean, `AppendState` pushes onto an array, `ShowToast` gives feedback. **Server actions** cross a network boundary: `CallTool` invokes an MCP tool, `Fetch` makes an HTTP request. They're asynchronous, they can fail, and the renderer handles both outcomes through `on_success` and `on_error` callbacks.

Most real interactions use both. A "Save" button sets a loading flag (instant), calls the server (async), then shows success or failure (instant, based on the outcome). Actions can be sequenced as lists and chained through callbacks.

The [Actions](/concepts/actions) guide covers both families, chaining, the `$event` variable, and common patterns like loading state and optimistic updates.

## How it runs

These four concepts are split across two environments.

**Build time** is when your Python code runs. You create components, nest them into a tree, attach state references and actions, and when you're done, Prefab serializes everything to a JSON document. Your server's job ends there.

**Render time** is everything after. A JavaScript renderer receives that document and turns it into a live interface: a React application running in the browser or inside an MCP host like Claude Desktop. The renderer manages state, evaluates expressions, handles interactions, and updates the display. None of that involves your server unless a `CallTool` or `Fetch` action explicitly calls back.

The cycle runs continuously:

1. Python builds a component tree and the renderer displays it
2. A user interacts: clicks, types, drags
3. An action fires: state updates directly, or a request goes out to the server
4. State changes trigger expression re-evaluation; the display updates
5. For server actions: the tool returns new content or state, and the renderer merges it

Steps 2 through 4 happen without your server involved. Step 5 only occurs when a server action fires, and when it does, it's just another state update from the renderer's perspective.

## MCP Apps

This architecture is what makes Prefab a natural foundation for MCP Apps. Your MCP tools return component trees, and those trees become live interfaces inside Claude Desktop, ChatGPT, and any host that supports the MCP App protocol. The renderer runs inside the host; your Python server handles the tool calls. The programming model is identical to a standalone app: the same components, the same state system, the same actions. See [FastMCP](/running/fastmcp) for the specifics.
