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

# Components

> Building UIs from components — what they are, how they nest, and how Python's with statement maps to the component tree.

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

Components are the building blocks of every Prefab UI. A `Button`, a `Card`, a `Slider` — each is a self-contained piece that knows how to render itself. You build interfaces by creating components and nesting them inside each other, the same way you'd compose functions in Python.

Every component is a Python class you instantiate with keyword arguments. Simple components like `Text` and `Badge` stand alone. Container components like `Card`, `Row`, and `Column` accept children — and you add children using Python's `with` statement. Nesting in code mirrors nesting on screen.

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import Card, CardContent, Text, Badge, Row

with Card():
    with CardContent():
        with Row(gap=2, align="center"):
            Text("Status")
            Badge("Active", variant="success")
```

Every component also accepts a `css_class` prop for styling via [Tailwind utility classes](/styling/css), so you can adjust spacing, colors, and layout without leaving Python.

## Rows and Columns

Every interface needs a way to arrange things on screen. Prefab provides two fundamental layout containers: `Row` arranges children horizontally, and `Column` stacks them vertically. Both accept a `gap` parameter that controls the spacing between children; the number maps to [Tailwind's spacing scale](/styling/css), so `gap=4` means `1rem`.

To place components inside a container, use Python's `with` statement. Any component created inside a `with` block automatically becomes a child of that container.

Here's a row of three green divs:

<ComponentPreview json={{"view":{"cssClass":"gap-4","type":"Row","children":[{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRGl2LCBSb3cKCndpdGggUm93KGdhcD00KToKICAgIERpdihjc3NfY2xhc3M9ImJnLWVtZXJhbGQtNTAwIHJvdW5kZWQtbWQgaC0xMCB3LTI0IikKICAgIERpdihjc3NfY2xhc3M9ImJnLWVtZXJhbGQtNTAwIHJvdW5kZWQtbWQgaC0xMCB3LTI0IikKICAgIERpdihjc3NfY2xhc3M9ImJnLWVtZXJhbGQtNTAwIHJvdW5kZWQtbWQgaC0xMCB3LTI0IikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Div, Row

    with Row(gap=4):
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-4",
        "type": "Row",
        "children": [
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"},
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"},
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"}
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

And here's a column. By default, a `Column` stretches to fill the available width; the `css_class="w-auto"` override shrinks it to fit its content instead.

<ComponentPreview json={{"view":{"cssClass":"gap-4 w-auto","type":"Column","children":[{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-emerald-500 rounded-md h-10 w-24","type":"Div"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBEaXYKCndpdGggQ29sdW1uKGdhcD00LCBjc3NfY2xhc3M9InctYXV0byIpOgogICAgRGl2KGNzc19jbGFzcz0iYmctZW1lcmFsZC01MDAgcm91bmRlZC1tZCBoLTEwIHctMjQiKQogICAgRGl2KGNzc19jbGFzcz0iYmctZW1lcmFsZC01MDAgcm91bmRlZC1tZCBoLTEwIHctMjQiKQogICAgRGl2KGNzc19jbGFzcz0iYmctZW1lcmFsZC01MDAgcm91bmRlZC1tZCBoLTEwIHctMjQiKQo">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Column, Div

    with Column(gap=4, css_class="w-auto"):
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
        Div(css_class="bg-emerald-500 rounded-md h-10 w-24")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-4 w-auto",
        "type": "Column",
        "children": [
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"},
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"},
          {"cssClass": "bg-emerald-500 rounded-md h-10 w-24", "type": "Div"}
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

## Nesting

Containers nest inside each other, and there's no limit to how deep you can go. The structure of your Python code directly mirrors the structure of the rendered UI: each level of indentation corresponds to a level of visual nesting. This makes it easy to read code and know exactly what the output will look like.

<ComponentPreview json={{"view":{"cssClass":"gap-4 w-auto","type":"Column","children":[{"cssClass":"gap-4","type":"Row","children":[{"cssClass":"bg-blue-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-blue-500 rounded-md h-10 w-24","type":"Div"},{"cssClass":"bg-blue-500 rounded-md h-10 w-24","type":"Div"}]},{"cssClass":"bg-purple-500 rounded-md h-10","type":"Div"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBEaXYsIFJvdwoKd2l0aCBDb2x1bW4oZ2FwPTQsIGNzc19jbGFzcz0idy1hdXRvIik6CiAgICB3aXRoIFJvdyhnYXA9NCk6CiAgICAgICAgRGl2KGNzc19jbGFzcz0iYmctYmx1ZS01MDAgcm91bmRlZC1tZCBoLTEwIHctMjQiKQogICAgICAgIERpdihjc3NfY2xhc3M9ImJnLWJsdWUtNTAwIHJvdW5kZWQtbWQgaC0xMCB3LTI0IikKICAgICAgICBEaXYoY3NzX2NsYXNzPSJiZy1ibHVlLTUwMCByb3VuZGVkLW1kIGgtMTAgdy0yNCIpCiAgICBEaXYoY3NzX2NsYXNzPSJiZy1wdXJwbGUtNTAwIHJvdW5kZWQtbWQgaC0xMCIpCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Column, Div, Row

    with Column(gap=4, css_class="w-auto"):
        with Row(gap=4):
            Div(css_class="bg-blue-500 rounded-md h-10 w-24")
            Div(css_class="bg-blue-500 rounded-md h-10 w-24")
            Div(css_class="bg-blue-500 rounded-md h-10 w-24")
        Div(css_class="bg-purple-500 rounded-md h-10")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-4 w-auto",
        "type": "Column",
        "children": [
          {
            "cssClass": "gap-4",
            "type": "Row",
            "children": [
              {"cssClass": "bg-blue-500 rounded-md h-10 w-24", "type": "Div"},
              {"cssClass": "bg-blue-500 rounded-md h-10 w-24", "type": "Div"},
              {"cssClass": "bg-blue-500 rounded-md h-10 w-24", "type": "Div"}
            ]
          },
          {"cssClass": "bg-purple-500 rounded-md h-10", "type": "Div"}
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

The blue divs sit in a horizontal row because they're inside the `Row` block. The purple div sits below them because it's a direct child of the outer `Column`. Each component attaches to whichever container is currently open; the innermost `with` block wins.

This is the core idea of Prefab composition: nesting containers. Every Prefab interface, from a simple form to a full dashboard, is built by nesting components inside other components.

## Adding content

The layout patterns above work with any component. Prefab ships with a large library of prebuilt components: [typography](/components/typography), [badges](/components/badge), [progress bars](/components/progress), [inputs](/components/input), [charts](/components/bar-chart), [data tables](/components/data-table), and [many more](/playground). You can browse them all in the [Playground](/playground).

These components compose with Row and Column exactly the way the colored placeholders did. Prefab also includes layout components beyond these two, such as [Grid](/components/grid), [Dashboard](/components/dashboard-grid), and [Pages](/components/pages), for more complex arrangements; they appear later on this page or in their own docs.

Here's the same Column and Row pattern, with real content:

<ComponentPreview json={{"view":{"cssClass":"gap-4 w-auto","type":"Column","children":[{"cssClass":"gap-3 items-center","type":"Row","children":[{"content":"API Server","type":"H3"},{"type":"Badge","label":"Healthy","variant":"success"}]},{"content":"Uptime: 99.97%","type":"Muted"},{"type":"Progress","value":99.97,"variant":"default","size":"default"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQmFkZ2UsIENvbHVtbiwgSDMsIE11dGVkLCBQcm9ncmVzcywgUm93Cgp3aXRoIENvbHVtbihnYXA9NCwgY3NzX2NsYXNzPSJ3LWF1dG8iKToKICAgIHdpdGggUm93KGdhcD0zLCBhbGlnbj0iY2VudGVyIik6CiAgICAgICAgSDMoIkFQSSBTZXJ2ZXIiKQogICAgICAgIEJhZGdlKCJIZWFsdGh5IiwgdmFyaWFudD0ic3VjY2VzcyIpCiAgICBNdXRlZCgiVXB0aW1lOiA5OS45NyUiKQogICAgUHJvZ3Jlc3ModmFsdWU9OTkuOTcpCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Badge, Column, H3, Muted, Progress, Row

    with Column(gap=4, css_class="w-auto"):
        with Row(gap=3, align="center"):
            H3("API Server")
            Badge("Healthy", variant="success")
        Muted("Uptime: 99.97%")
        Progress(value=99.97)
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-4 w-auto",
        "type": "Column",
        "children": [
          {
            "cssClass": "gap-3 items-center",
            "type": "Row",
            "children": [
              {"content": "API Server", "type": "H3"},
              {"type": "Badge", "label": "Healthy", "variant": "success"}
            ]
          },
          {"content": "Uptime: 99.97%", "type": "Muted"},
          {"type": "Progress", "value": 99.97, "variant": "default", "size": "default"}
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

### Compound components

Some Prefab components are designed to be composed from sub-components. [Card](/components/card) expects `CardHeader`, `CardContent`, and `CardFooter` children. [Tabs](/components/tabs) expects `Tab` children. [Accordion](/components/accordion) expects `AccordionItem` children. The sub-components follow the same nesting model: each one opens a `with` block that collects its own children.

Cards are the most common compound component, wrapping related content in a bordered container with distinct header, content, and footer sections. The card below wraps the same status content from the previous example.

<ComponentPreview json={{"view":{"cssClass":"w-72","type":"Card","children":[{"type":"CardHeader","children":[{"cssClass":"gap-3 items-center","type":"Row","children":[{"type":"CardTitle","content":"API Server"},{"type":"Badge","label":"Healthy","variant":"success"}]}]},{"type":"CardContent","children":[{"cssClass":"gap-3","type":"Column","children":[{"content":"Uptime: 99.97%","type":"Muted"},{"type":"Progress","value":99.97,"variant":"default","size":"default"}]}]},{"type":"CardFooter","children":[{"type":"Button","label":"View Logs","variant":"outline","size":"default","disabled":false}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgKAogICAgQmFkZ2UsCiAgICBCdXR0b24sCiAgICBDYXJkLAogICAgQ2FyZENvbnRlbnQsCiAgICBDYXJkRm9vdGVyLAogICAgQ2FyZEhlYWRlciwKICAgIENhcmRUaXRsZSwKICAgIENvbHVtbiwKICAgIE11dGVkLAogICAgUHJvZ3Jlc3MsCiAgICBSb3csCikKCndpdGggQ2FyZChjc3NfY2xhc3M9InctNzIiKToKICAgIHdpdGggQ2FyZEhlYWRlcigpOgogICAgICAgIHdpdGggUm93KGdhcD0zLCBhbGlnbj0iY2VudGVyIik6CiAgICAgICAgICAgIENhcmRUaXRsZSgiQVBJIFNlcnZlciIpCiAgICAgICAgICAgIEJhZGdlKCJIZWFsdGh5IiwgdmFyaWFudD0ic3VjY2VzcyIpCiAgICB3aXRoIENhcmRDb250ZW50KCk6CiAgICAgICAgd2l0aCBDb2x1bW4oZ2FwPTMpOgogICAgICAgICAgICBNdXRlZCgiVXB0aW1lOiA5OS45NyUiKQogICAgICAgICAgICBQcm9ncmVzcyh2YWx1ZT05OS45NykKICAgIHdpdGggQ2FyZEZvb3RlcigpOgogICAgICAgIEJ1dHRvbigiVmlldyBMb2dzIiwgdmFyaWFudD0ib3V0bGluZSIpCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import (
        Badge,
        Button,
        Card,
        CardContent,
        CardFooter,
        CardHeader,
        CardTitle,
        Column,
        Muted,
        Progress,
        Row,
    )

    with Card(css_class="w-72"):
        with CardHeader():
            with Row(gap=3, align="center"):
                CardTitle("API Server")
                Badge("Healthy", variant="success")
        with CardContent():
            with Column(gap=3):
                Muted("Uptime: 99.97%")
                Progress(value=99.97)
        with CardFooter():
            Button("View Logs", variant="outline")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "w-72",
        "type": "Card",
        "children": [
          {
            "type": "CardHeader",
            "children": [
              {
                "cssClass": "gap-3 items-center",
                "type": "Row",
                "children": [
                  {"type": "CardTitle", "content": "API Server"},
                  {"type": "Badge", "label": "Healthy", "variant": "success"}
                ]
              }
            ]
          },
          {
            "type": "CardContent",
            "children": [
              {
                "cssClass": "gap-3",
                "type": "Column",
                "children": [
                  {"content": "Uptime: 99.97%", "type": "Muted"},
                  {"type": "Progress", "value": 99.97, "variant": "default", "size": "default"}
                ]
              }
            ]
          },
          {
            "type": "CardFooter",
            "children": [
              {
                "type": "Button",
                "label": "View Logs",
                "variant": "outline",
                "size": "default",
                "disabled": false
              }
            ]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

The nesting is deeper, but the principle hasn't changed: every `with` block opens a container, and components created inside it become children.

## Grids and control flow

When you need to arrange multiple items in a uniform layout, [Grid](/components/grid) places children in equal-width columns that wrap automatically. Pass `columns=3` for a three-column grid, or use `min_column_width` for responsive layouts that adapt to the available space.

### Python loops

Since the DSL is plain Python, you can use a `for` loop to produce components from data. The loop runs once when the component tree is constructed, and the result is baked into the output: the renderer never sees the loop, just the components it produced. This works well when the data is static and known at build time.

<ComponentPreview json={{"view":{"cssClass":"gap-4 grid-cols-3","type":"Grid","children":[{"type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"API Server"}]},{"type":"CardContent","children":[{"cssClass":"gap-2","type":"Column","children":[{"content":"99.97%","type":"Large"},{"content":"uptime","type":"Muted"},{"type":"Progress","value":99.97,"variant":"default","size":"default"}]}]}]},{"type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"Worker"}]},{"type":"CardContent","children":[{"cssClass":"gap-2","type":"Column","children":[{"content":"94.2%","type":"Large"},{"content":"uptime","type":"Muted"},{"type":"Progress","value":94.2,"variant":"default","size":"default"}]}]}]},{"type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"Scheduler"}]},{"type":"CardContent","children":[{"cssClass":"gap-2","type":"Column","children":[{"content":"99.8%","type":"Large"},{"content":"uptime","type":"Muted"},{"type":"Progress","value":99.8,"variant":"default","size":"default"}]}]}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgKAogICAgQ2FyZCwKICAgIENhcmRDb250ZW50LAogICAgQ2FyZEhlYWRlciwKICAgIENhcmRUaXRsZSwKICAgIENvbHVtbiwKICAgIEdyaWQsCiAgICBMYXJnZSwKICAgIE11dGVkLAogICAgUHJvZ3Jlc3MsCikKCnNlcnZpY2VzID0gWwogICAgeyJuYW1lIjogIkFQSSBTZXJ2ZXIiLCAidXB0aW1lIjogOTkuOTd9LAogICAgeyJuYW1lIjogIldvcmtlciIsICJ1cHRpbWUiOiA5NC4yfSwKICAgIHsibmFtZSI6ICJTY2hlZHVsZXIiLCAidXB0aW1lIjogOTkuOH0sCl0KCndpdGggR3JpZChjb2x1bW5zPTMsIGdhcD00KToKICAgIGZvciBzdmMgaW4gc2VydmljZXM6CiAgICAgICAgd2l0aCBDYXJkKCk6CiAgICAgICAgICAgIHdpdGggQ2FyZEhlYWRlcigpOgogICAgICAgICAgICAgICAgQ2FyZFRpdGxlKHN2Y1sibmFtZSJdKQogICAgICAgICAgICB3aXRoIENhcmRDb250ZW50KCk6CiAgICAgICAgICAgICAgICB3aXRoIENvbHVtbihnYXA9Mik6CiAgICAgICAgICAgICAgICAgICAgTGFyZ2UoZiJ7c3ZjWyd1cHRpbWUnXX0lIikKICAgICAgICAgICAgICAgICAgICBNdXRlZCgidXB0aW1lIikKICAgICAgICAgICAgICAgICAgICBQcm9ncmVzcyh2YWx1ZT1zdmNbInVwdGltZSJdKQo">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import (
        Card,
        CardContent,
        CardHeader,
        CardTitle,
        Column,
        Grid,
        Large,
        Muted,
        Progress,
    )

    services = [
        {"name": "API Server", "uptime": 99.97},
        {"name": "Worker", "uptime": 94.2},
        {"name": "Scheduler", "uptime": 99.8},
    ]

    with Grid(columns=3, gap=4):
        for svc in services:
            with Card():
                with CardHeader():
                    CardTitle(svc["name"])
                with CardContent():
                    with Column(gap=2):
                        Large(f"{svc['uptime']}%")
                        Muted("uptime")
                        Progress(value=svc["uptime"])
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-4 grid-cols-3",
        "type": "Grid",
        "children": [
          {
            "type": "Card",
            "children": [
              {
                "type": "CardHeader",
                "children": [{"type": "CardTitle", "content": "API Server"}]
              },
              {
                "type": "CardContent",
                "children": [
                  {
                    "cssClass": "gap-2",
                    "type": "Column",
                    "children": [
                      {"content": "99.97%", "type": "Large"},
                      {"content": "uptime", "type": "Muted"},
                      {"type": "Progress", "value": 99.97, "variant": "default", "size": "default"}
                    ]
                  }
                ]
              }
            ]
          },
          {
            "type": "Card",
            "children": [
              {"type": "CardHeader", "children": [{"type": "CardTitle", "content": "Worker"}]},
              {
                "type": "CardContent",
                "children": [
                  {
                    "cssClass": "gap-2",
                    "type": "Column",
                    "children": [
                      {"content": "94.2%", "type": "Large"},
                      {"content": "uptime", "type": "Muted"},
                      {"type": "Progress", "value": 94.2, "variant": "default", "size": "default"}
                    ]
                  }
                ]
              }
            ]
          },
          {
            "type": "Card",
            "children": [
              {
                "type": "CardHeader",
                "children": [{"type": "CardTitle", "content": "Scheduler"}]
              },
              {
                "type": "CardContent",
                "children": [
                  {
                    "cssClass": "gap-2",
                    "type": "Column",
                    "children": [
                      {"content": "99.8%", "type": "Large"},
                      {"content": "uptime", "type": "Muted"},
                      {"type": "Progress", "value": 99.8, "variant": "default", "size": "default"}
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

### ForEach

When the data lives in client-side state and can change dynamically, for example a list of items the user can add to or remove from, you need iteration that happens at render time. [`ForEach`](/components/foreach) takes a state key pointing to a list and renders its children once per item, re-rendering automatically whenever the list changes.

Inside a `ForEach` block, `{{ $item }}` refers to the current item (or its fields directly if the item is a dict), and `{{ $index }}` gives the zero-based position. Because `ForEach` is a component in the tree (not a Python loop that runs and disappears), the renderer knows about the iteration. If an action appends a fourth task to the list, `ForEach` renders a new row for it automatically.

<ComponentPreview json={{"view":{"cssClass":"gap-2","type":"Column","children":[{"let":{"_loop_8":"{{ $item }}","_loop_8_idx":"{{ $index }}"},"type":"ForEach","key":"tasks","children":[{"content":"{{ $index + 1 }}. {{ $item }}","type":"Text"}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBUZXh0CmZyb20gcHJlZmFiX3VpLmNvbXBvbmVudHMuY29udHJvbF9mbG93IGltcG9ydCBGb3JFYWNoCgoKd2l0aCBDb2x1bW4oZ2FwPTIpOgogICAgd2l0aCBGb3JFYWNoKCJ0YXNrcyIpOgogICAgICAgIFRleHQoInt7ICRpbmRleCArIDEgfX0uIHt7ICRpdGVtIH19IikK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Column, Text
    from prefab_ui.components.control_flow import ForEach


    with Column(gap=2):
        with ForEach("tasks"):
            Text("{{ $index + 1 }}. {{ $item }}")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-2",
        "type": "Column",
        "children": [
          {
            "let": {"_loop_8": "{{ $item }}", "_loop_8_idx": "{{ $index }}"},
            "type": "ForEach",
            "key": "tasks",
            "children": [{"content": "{{ $index + 1 }}. {{ $item }}", "type": "Text"}]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

### Conditionals

[`If`](/components/conditional), `Elif`, and `Else` provide render-time conditionals. `If` takes a boolean expression and only renders its children when the expression is truthy. Consecutive `If/Elif/Else` siblings form a single conditional chain: the renderer evaluates them in order and renders the first match; if nothing matches, the `Else` branch renders.

Change the select below to see the conditional switch between branches.

<ComponentPreview json={{"view":{"cssClass":"gap-3","type":"Column","children":[{"name":"status","type":"Select","placeholder":"Pick a status","size":"default","disabled":false,"required":false,"invalid":false,"children":[{"type":"SelectOption","value":"healthy","label":"Healthy","selected":false,"disabled":false},{"type":"SelectOption","value":"warning","label":"Warning","selected":false,"disabled":false},{"type":"SelectOption","value":"error","label":"Error","selected":false,"disabled":false}]},{"type":"Condition","cases":[{"when":"{{ status == 'error' }}","children":[{"type":"Alert","variant":"destructive","children":[{"type":"AlertTitle","content":"System down"}]}]},{"when":"{{ status == 'warning' }}","children":[{"type":"Alert","variant":"warning","children":[{"type":"AlertTitle","content":"Degraded performance"}]}]}],"else":[{"type":"Badge","label":"All systems go","variant":"success"}]}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgU1RBVEUgYXMgc3RhdGUsIEFsZXJ0LCBBbGVydFRpdGxlLCBCYWRnZSwgQ29sdW1uLCBTZWxlY3QsIFNlbGVjdE9wdGlvbgpmcm9tIHByZWZhYl91aS5jb21wb25lbnRzLmNvbnRyb2xfZmxvdyBpbXBvcnQgRWxpZiwgRWxzZSwgSWYKCgp3aXRoIENvbHVtbihnYXA9Myk6CiAgICB3aXRoIFNlbGVjdChuYW1lPSJzdGF0dXMiLCBwbGFjZWhvbGRlcj0iUGljayBhIHN0YXR1cyIpOgogICAgICAgIFNlbGVjdE9wdGlvbih2YWx1ZT0iaGVhbHRoeSIsIGxhYmVsPSJIZWFsdGh5IikKICAgICAgICBTZWxlY3RPcHRpb24odmFsdWU9Indhcm5pbmciLCBsYWJlbD0iV2FybmluZyIpCiAgICAgICAgU2VsZWN0T3B0aW9uKHZhbHVlPSJlcnJvciIsIGxhYmVsPSJFcnJvciIpCiAgICB3aXRoIElmKHN0YXRlLnN0YXR1cyA9PSAiZXJyb3IiKToKICAgICAgICB3aXRoIEFsZXJ0KHZhcmlhbnQ9ImRlc3RydWN0aXZlIik6CiAgICAgICAgICAgIEFsZXJ0VGl0bGUoIlN5c3RlbSBkb3duIikKICAgIHdpdGggRWxpZihzdGF0ZS5zdGF0dXMgPT0gIndhcm5pbmciKToKICAgICAgICB3aXRoIEFsZXJ0KHZhcmlhbnQ9Indhcm5pbmciKToKICAgICAgICAgICAgQWxlcnRUaXRsZSgiRGVncmFkZWQgcGVyZm9ybWFuY2UiKQogICAgd2l0aCBFbHNlKCk6CiAgICAgICAgQmFkZ2UoIkFsbCBzeXN0ZW1zIGdvIiwgdmFyaWFudD0ic3VjY2VzcyIpCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import STATE as state, Alert, AlertTitle, Badge, Column, Select, SelectOption
    from prefab_ui.components.control_flow import Elif, Else, If


    with Column(gap=3):
        with Select(name="status", placeholder="Pick a status"):
            SelectOption(value="healthy", label="Healthy")
            SelectOption(value="warning", label="Warning")
            SelectOption(value="error", label="Error")
        with If(state.status == "error"):
            with Alert(variant="destructive"):
                AlertTitle("System down")
        with Elif(state.status == "warning"):
            with Alert(variant="warning"):
                AlertTitle("Degraded performance")
        with Else():
            Badge("All systems go", variant="success")
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-3",
        "type": "Column",
        "children": [
          {
            "name": "status",
            "type": "Select",
            "placeholder": "Pick a status",
            "size": "default",
            "disabled": false,
            "required": false,
            "invalid": false,
            "children": [
              {
                "type": "SelectOption",
                "value": "healthy",
                "label": "Healthy",
                "selected": false,
                "disabled": false
              },
              {
                "type": "SelectOption",
                "value": "warning",
                "label": "Warning",
                "selected": false,
                "disabled": false
              },
              {
                "type": "SelectOption",
                "value": "error",
                "label": "Error",
                "selected": false,
                "disabled": false
              }
            ]
          },
          {
            "type": "Condition",
            "cases": [
              {
                "when": "{{ status == 'error' }}",
                "children": [
                  {
                    "type": "Alert",
                    "variant": "destructive",
                    "children": [{"type": "AlertTitle", "content": "System down"}]
                  }
                ]
              },
              {
                "when": "{{ status == 'warning' }}",
                "children": [
                  {
                    "type": "Alert",
                    "variant": "warning",
                    "children": [{"type": "AlertTitle", "content": "Degraded performance"}]
                  }
                ]
              }
            ],
            "else": [{"type": "Badge", "label": "All systems go", "variant": "success"}]
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

### Build-time vs render-time

Prefab UIs are declarative: your Python code runs once to build a component tree, then the renderer takes over. Python `for` loops and `if` statements produce a fixed result at build time. `ForEach` and `If/Elif/Else` are reactive: they live in the component tree and respond to state changes at render time. Use Python control flow for data that is fixed at build time, and use the control-flow components when the UI needs to respond to dynamic state. For any logic that needs to run after build time, use [server actions](/concepts/actions).

## Forward references

Sometimes you need a component's reactive value before that component exists. Python executes top-to-bottom, so if a label above a slider needs `slider.rx`, you have a problem — the slider hasn't been created at the point where the label is defined. Prefab offers two escape hatches: lambda Rx for reactive values, and defer/insert for structural rearrangement.

### Lambda Rx

The common case is needing a component's `.rx` before it's created. Wrap the reference in a lambda, and Rx defers resolution until render time:

<ComponentPreview json={{"view":{"cssClass":"gap-2","type":"Column","children":[{"cssClass":"justify-between","type":"Row","children":[{"type":"Label","text":"Volume","optional":false},{"cssClass":"font-bold","content":"{{ slider_23 / 100 | percent }}","type":"Text"}]},{"name":"slider_23","value":75.0,"type":"Slider","min":0,"max":100,"disabled":false,"size":"default"}]},"state":{"slider_23":75.0}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBMYWJlbCwgUm93LCBTbGlkZXIsIFRleHQKZnJvbSBwcmVmYWJfdWkucnggaW1wb3J0IFJ4Cgp2b2wgPSBSeChsYW1iZGE6IHZvbHVtZSkKCndpdGggQ29sdW1uKGdhcD0yKToKICAgIHdpdGggUm93KGNzc19jbGFzcz0ianVzdGlmeS1iZXR3ZWVuIik6CiAgICAgICAgTGFiZWwoIlZvbHVtZSIpCiAgICAgICAgVGV4dCgodm9sIC8gMTAwKS5wZXJjZW50KCksIGJvbGQ9VHJ1ZSkKICAgIHZvbHVtZSA9IFNsaWRlcih2YWx1ZT03NSkK">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Column, Label, Row, Slider, Text
    from prefab_ui.rx import Rx

    vol = Rx(lambda: volume)

    with Column(gap=2):
        with Row(css_class="justify-between"):
            Label("Volume")
            Text((vol / 100).percent(), bold=True)
        volume = Slider(value=75)
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-2",
        "type": "Column",
        "children": [
          {
            "cssClass": "justify-between",
            "type": "Row",
            "children": [
              {"type": "Label", "text": "Volume", "optional": false},
              {
                "cssClass": "font-bold",
                "content": "{{ slider_23 / 100 | percent }}",
                "type": "Text"
              }
            ]
          },
          {
            "name": "slider_23",
            "value": 75.0,
            "type": "Slider",
            "min": 0,
            "max": 100,
            "disabled": false,
            "size": "default"
          }
        ]
      },
      "state": {"slider_23": 75.0}
    }
    ```
  </CodeGroup>
</ComponentPreview>

`Rx(lambda: volume)` doesn't resolve right away — it captures the *name* `volume` and waits. When the lambda returns a stateful component, Rx extracts its `.rx` automatically, so `Rx(lambda: volume)` and `Rx(lambda: volume.rx)` are equivalent. Operations like `/ 100` and `.percent()` compose lazily too, so you can build entire expression chains against a component that doesn't exist yet.

### Structural rearrangement with defer/insert

Lambda Rx covers the common case of forward-referencing a value. Occasionally you need something different: building an entire component subtree outside the current tree and grafting it in later. That's what `defer()` and `insert()` are for:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import Column, Text, defer, insert

with Column() as page:
    Text("Header content")

    with defer():
        sidebar = Column()
        with sidebar:
            Text("Sidebar content")

    insert(sidebar)
```

Inside a `defer()` block, auto-attachment is suspended — components can still nest children inside each other with `with`, they just won't attach to the outer container. `insert()` grafts them in where you want them.

Most of the time you won't need `defer`/`insert`. Lambda Rx covers the `.rx` forward-reference pattern, and normal composition handles everything else.
