Generative UI · One web component

Render LLM-generated UI in any framework.

<aktion-app> is a single web component that turns a compact, streaming-first DSL into a rich, interactive UI inside its shadow DOM. Works in React, Vue, Angular, Svelte, plain HTML — or no framework at all.

Drop-in CDN install

One script tag and one custom element — that’s the whole integration.

<script type="module" src="https://asfand-dev.github.io/aktion/dist/aktion.js"></script>
<aktion-app theme="light"></aktion-app>
<script>
  const el = document.querySelector("aktion-app");
  el.setResponse(`
      _app_ = Stack([greeting])
      greeting = Card([CardHeader("Hello", subtitle: "Generative UI in plain HTML")])
  `);
</script>

Why Aktion?

Framework-agnostic

Built on the standard Custom Elements API — works wherever HTML works. Shadow DOM keeps styles isolated.

Streaming-first language

Parser, evaluator, reactive state, HTTP primitive, and 30+ @ builtins ship in one bundle.

Renders progressively

Feed LLM tokens via appendChunk() — the UI renders top-down as the response streams.

130+ components

Layout, forms, charts, data, patterns (Hero, KanbanBoard, PricingTable…), app shell, and editors.

Seven themes built in

Switch via the theme attribute or pass a custom JSON token map. In-script Theme({...}) for brand overrides.

System prompt included

Full system_prompt.txt and compact system_prompt_chat.txt stay in lock-step with the library.

Hash-based routing

Multi-page UIs via pages = _router_({ "/": Page() }) + NavLink. Always on, deep links and back/forward just work.

JavaScript escape hatch

js{ … } blocks inside effect / action bodies for the rare cases the declarative surface doesn’t cover.

Live demo

The component below was rendered from a few lines of Aktion. Use the theme picker to swap palettes — the preview updates instantly.

Welcome card with KPIs & chart

_app_ = Stack([welcome, kpis, chart])
welcome = Card([
  CardHeader("Hello there", subtitle: "Generative UI rendered from Aktion"),
  Markdown("This card was streamed in **plain text** and rendered as a real UI.")
])
kpis = Stats([
  StatCard("Active users", value: "12,540", trend: "up", delta: "+18% vs last week"),
  StatCard("Revenue", value: "$48.2k", trend: "flat", delta: "0% vs last week"),
  StatCard("Errors", value: "12", trend: "down", delta: "-32% vs last week")
], layout: "grid")
chart = Card([
  CardHeader("Daily traffic"),
  LineChart(labels: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"], series: [
    Series("Visits", values: [120,180,160,210,250,200,230]),
    Series("Signups", values: [12,18,15,22,28,21,24])
  ])
])

How it works

Aktion is line-oriented and error-tolerant. Each statement commits to the DOM as soon as it streams in. Three layers cooperate:

1. Declarative tree

Pure-data composition of components: _app_ = Stack([header, body]). Re-computed on every render.

2. Reactive state & HTTP

One sigil: $name = value. One network primitive: http({ url, method, … }). Changes schedule a re-render automatically.

3. Actions & effects

action Name() { … } for click-driven mutations, effect [ ...deps ] { … } for declarative background work.

Where to next?