---
title: v0.1
description: The original OpenUI Lang specification for declarative UI generation in chat responses.
---

<Callout type="info">
  This is the **v0.1** specification, the original language for generating static UI from LLM
  output. For the latest version with reactive state, data queries, and interactive apps, see
  [v0.5](/docs/openui-lang/specification-v05).
</Callout>

OpenUI Lang is the output format the LLM generates. It is a compact, declarative, line-oriented DSL designed specifically for streaming and token efficiency.

While you rarely write this language manually, understanding the syntax is helpful for debugging raw LLM outputs or building custom parsers.

## Syntax Overview

The language consists of a series of **assignment statements**. Every line binds a unique identifier to an expression.

```text
root = Root([header, chart])           // 1. Entry Point
header = Header("Q4 Revenue", "YTD")   // 2. Component Call
chart = BarChart(labels, [s1, s2])     // 3. Forward Reference
labels = ["Jan", "Feb", "Mar"]         // 4. Data Definition
s1 = Series("Product A", [10, 20, 30])
s2 = Series("Product B", [5, 15, 25])
```

## Core Rules

1. **One statement per line:** `identifier = Expression`
2. **Root Entry Point:** The first statement **must** assign to the identifier `root` (or call the Root component). If missing, nothing renders.
3. **Top-Down Generation:** Statements are generally written top-down (Layout -> Components -> Data) to maximize perceived performance during streaming.
4. **Positional Arguments:** Arguments are mapped to component props by position, defined by the order of keys in your Zod schema.

---

## Expressions & Types

OpenUI Lang supports a strict subset of JavaScript values.

| Type               | Syntax              | Example                       |
| :----------------- | :------------------ | :---------------------------- |
| **Component Call** | `Type(arg1, arg2)`  | `Header("Title", "Subtitle")` |
| **String**         | `"text"`            | `"Hello world"`               |
| **Number**         | `123`, `12.5`, `-5` | `42`                          |
| **Boolean**        | `true` / `false`    | `true`                        |
| **Null**           | `null`              | `null`                        |
| **Array**          | `[a, b, c]`         | `["Jan", "Feb", "Mar"]`       |
| **Object**         | `{key: val}`        | `{variant: "info", id: 1}`    |
| **Reference**      | `identifier`        | `myTableData`                 |

---

## Component Resolution

The parser maps **Positional Arguments** in OpenUI Lang to **Named Props** in React using your Zod definitions.

## The Mapping Logic

The order of keys in your `z.object` schema defines the expected argument order.

**1. The Schema (Zod)**

```typescript
const HeaderSchema = z.object({
  title: z.string(), // Position 0
  subtitle: z.string().optional(), // Position 1
});
```

**2. The Output (OpenUI Lang)**

```text
h1 = Header("Dashboard", "Overview")
```

**3. The Result (React Props)**

```json
{
  "title": "Dashboard",
  "subtitle": "Overview"
}
```

### Critical implications for component design

- **Key order in z.object is the API contract.** Changing key order breaks all existing LLM outputs.
- **Required props must come before optional props** in the schema, since trailing optional args can be omitted.
- **The LLM learns positions from the auto-generated system prompt.** If the prompt and schema disagree (e.g., after a schema change without regenerating the prompt), output will be garbled.

## Optional Arguments

Optional arguments (trailing) can be omitted.

```text
// Valid: subtitle is undefined
h1 = Header("Dashboard")
```

---

## Streaming & Hoisting

OpenUI Lang allows **Forward References** (Hoisting). An identifier can be used as an argument _before_ it is defined in the stream. This is critical for progressive rendering.

## How it works

```text
root = Root([table]) // "table" is referenced here...
// ... (network latency) ...
table = Table(rows)  // ...but defined here.
```

1. **Step 1:** The renderer parses `root`. It sees `table` is undefined. It renders a **Skeleton/Placeholder** for the table.
2. **Step 2:** The `table` definition arrives. The renderer updates the React tree, replacing the skeleton with the actual `Table` component.
3. **Step 3:** `rows` arrives → Table fills in with data.

## Complete Syntax Example

A complex example showing nesting, arrays, and mixed types.

```text
root = Root([nav, dashboard])
nav  = Navbar("Acme Corp", [link1, link2])
link1 = Link("Home", "/")
link2 = Link("Settings", "/settings")

dashboard = Section([kpi_row, main_chart])
kpi_row   = Grid([stat1, stat2])
stat1     = StatCard("Revenue", "$1.2M", "up")
stat2     = StatCard("Users", "450k", "flat")

main_chart = LineChart(
  ["Mon", "Tue", "Wed"],
  [Series("Visits", [100, 450, 320])]
)
```
