---
title: "Introducing @std/dynamic: a JSON format for composable dashboards"
description: "@std/dynamic is the composable dashboard format powering genui.sh. Here's the shape, the primitives, and why it's designed the way it is."
date: 2026-04-30
url: https://genui.sh/blog/introducing-std-dynamic
---


Most "render from JSON" APIs give you two choices. You either get **a rigid template** you fill in by slot — the PDFMonkey / DocRaptor / APITemplate.io approach — or **raw Markdown / HTML**, which is expressive but produces documents, not dashboards.

There's a real middle ground, and it's what I've been building for [genui.sh](https://genui.sh). I'm calling it `@std/dynamic`, and it's the format I use internally to compose every dashboard the API renders. The landing page has a [live example](https://genui.sh/#pricing) — a real dashboard generated from a single POST, rendered by the same code any user gets.

This post is a quick tour of the format: the shape, the primitives, and the reasoning behind the design choices.

## The pitch

`@std/dynamic` lets you describe a dashboard as a flat dictionary of elements, keyed by ID, each with a `type`, a `props` object, and an optional `children` array pointing at other keys. One of the elements is designated as the root.

That's it. The whole format.

```json
{
  "template": "@std/dynamic",
  "title": "Weekly sales",
  "content": {
    "root": "page",
    "elements": {
      "page": {
        "type": "Stack",
        "props": { "direction": "vertical", "gap": "md" },
        "children": ["heading", "metrics"]
      },
      "heading": {
        "type": "Heading",
        "props": { "level": "h1", "text": "Weekly sales" }
      },
      "metrics": {
        "type": "Grid",
        "props": { "columns": 2, "gap": "md" },
        "children": ["revenue", "orders"]
      },
      "revenue": {
        "type": "Metric",
        "props": {
          "label": "Revenue",
          "value": "$142,840",
          "trend": "up",
          "trendValue": "+12%"
        }
      },
      "orders": {
        "type": "Metric",
        "props": {
          "label": "Orders",
          "value": "1,248",
          "trend": "up",
          "trendValue": "+8%"
        }
      }
    }
  }
}
```

POST that to `/v1/artifacts/share` and you get back a hosted URL rendering the dashboard. Primitives compose recursively — a `Grid` can contain `Card`s which contain `Chart`s, which is how you build multi-layer layouts from a single POST.

## The primitives

There are ~20 components in the current catalog, grouped by purpose:

**Layout** — `Card`, `Grid`, `Stack`, `Tabs`

**Content** — `Heading`, `Text`, `Markdown`, `Alert`, `Divider`, `Link`, `List`

**Data** — `Chart` (bar, line, area, pie, donut), `Table` (sortable, searchable, exportable), `Metric` (KPI card with trend), `Badge`, `Progress`, `Avatar`

**Interactive** — `Input`, `Button`, `FormField`, `Accordion`

Every component has a Zod schema for its props, and validation happens server-side before anything renders. Invalid trees fail fast with a 400 that tells you which element was malformed. This matters for the next section.

## Why flat-dict-plus-root, not nested JSON

The obvious way to describe a UI tree is nested:

```json
{
  "type": "Stack",
  "children": [
    { "type": "Heading", "props": { ... } },
    { "type": "Grid", "children": [
      { "type": "Metric", "props": { ... } },
      { "type": "Metric", "props": { ... } }
    ]}
  ]
}
```

This is what React does internally. It's fine for hand-authoring, but it has three real problems once you're generating trees programmatically:

1. **Emitting deeply-nested JSON recursively is annoying** in almost every language, especially when you're building a tree from database rows.
2. **LLMs are bad at deeply nested JSON.** The error rate on a 5-level nested structure is noticeably higher than on a flat dictionary with sibling references.
3. **Diffing and updating a flat dict** (keyed by ID) is trivial; diffing a nested tree is not.

The flat-dict shape solves all three. You emit elements in any order, reference them by key, and the renderer walks the graph starting from `root`. It also means you can generate a tree by appending to a dictionary, which matches how most ORM-to-UI code naturally works.

## Why this is good for LLM tool use

The format was designed from day one to be emitted by a language model. Three properties make it work well:

**1. The full grammar fits in a small system prompt.** You can describe the 20 components, their prop types, and the tree structure in ~800 tokens. That's small enough to include in every tool-use call without eating into your context budget.

**2. Strict Zod validation on the server.** If an LLM emits a malformed tree — wrong prop name, missing required field, nested component where `hasChildren: false` — the API returns a 400 with a specific error path. The agent can retry with the correction. No silent rendering of broken dashboards.

**3. No CSS, no custom rendering logic.** The LLM doesn't have to reason about layout, color, spacing, or responsive breakpoints. It picks from a fixed set of primitives with documented behavior. This is exactly the constraint that makes LLM output reliable — a narrow, strongly-typed action space.

A Claude or GPT tool call can now return a *dashboard* to the user instead of a paragraph of JSON. The tool-use result is a URL. The user clicks it. The user is not staring at `{"revenue": 142840, "orders": 1248}` wondering what to do with it.

## What's not in the format (yet)

- **No custom CSS or arbitrary HTML.** On purpose. The whole point is that the renderer owns the visual language so the output stays consistent.
- **No user-defined components.** Also on purpose, at least for now. A community component catalog might make sense later, but the bar is high — every new primitive makes the system prompt bigger and the LLM's reliability lower.
- **No client-side state or routing.** `Input` and `Button` render with real DOM, but they don't have submit handlers wired to user code. You'd use the format to render an interactive card that POSTs back to *your* API, not to build a full app.
- **No theming knobs.** The renderer uses the genui.sh design tokens. If you want your own theme, white-labeling is on the roadmap for the Pro tier.

These are real limits. If you need user-defined components or custom CSS, `@std/dynamic` is the wrong tool and you should reach for a full frontend framework. If you're emitting dashboards from a pipeline, a workflow, or an agent, the constraints are features.

## Try it

The playground at [genui.sh/dashboard/playground](https://genui.sh/dashboard/playground) has a live JSON editor on one side and a rendered preview on the other. You can switch the `template` dropdown to `@std/dynamic` and start composing — every edit re-renders in real time.

There's also a system-prompt generator at [genui.sh/dashboard/templates](https://genui.sh/dashboard/templates) that produces the exact `@std/dynamic` grammar for your chosen component subset, ready to paste into an LLM's system prompt or a tool-use schema.

Grab a free key (50 artifacts/month, no credit card) and POST your first tree at [genui.sh](https://genui.sh). Feedback welcome at support@genui.sh, especially on the primitive catalog — which components you reach for first, and which ones you expected and didn't find.
