LLui

The web framework designed for LLMs

LLui is the first web framework built from the ground up for AI-assisted development. Its architecture — strict types, pure functions, effects as data, and a flat component model — maps directly to how LLMs reason about code.

import { component, mountApp, div, button, text } from '@llui/dom'

type State = { count: number }
type Msg = { type: 'inc' } | { type: 'dec' }

const Counter = component<State, Msg, never>({
  name: 'Counter',
  init: () => [{ count: 0 }, []],
  update: (state, msg) => {
    switch (msg.type) {
      case 'inc':
        return [{ ...state, count: state.count + 1 }, []]
      case 'dec':
        return [{ ...state, count: state.count - 1 }, []]
    }
  },
  view: ({ state, send }) => [
    div({ class: 'counter' }, [
      button({ onClick: () => send({ type: 'dec' }) }, [text('-')]),
      text(state.at('count').map(String)),
      button({ onClick: () => send({ type: 'inc' }) }, [text('+')]),
    ]),
  ],
})

mountApp(document.getElementById('app')!, Counter)

Why LLMs produce better LLui code

One pattern for everything. Every component is initupdateview. No hooks, no lifecycle methods, no class hierarchies, no context providers. An LLM that understands one component understands them all.

Pure functions are predictable. update(state, msg) is a pure function — given the same state and message, it always returns the same result. LLMs don't need to reason about mutation, timing, or hidden side effects. They just pattern-match on message types.

Types constrain the output. State, Msg, and Effect are explicit discriminated unions. The type system rejects invalid states at compile time. An LLM can't accidentally produce a component that sends the wrong message or forgets to handle a case — TypeScript catches it.

Effects as data, not callbacks. Side effects are plain objects returned from update(), not imperative calls scattered through the code. An LLM can reason about what a component does by reading its return values. Testing is just deepEqual on the effect array.

No hidden runtime magic. view() runs once. There's no virtual DOM diffing, no re-rendering. Reactive bindings are explicit signals: text(state.at('count').map(String)). An LLM can see exactly which state drives which DOM node.

Flat composition. Components compose via view functions (a signal slice + send), not through nested provider trees. There's one level of indirection, not five. LLMs can follow the data flow in a single pass.

How it works

  • view() runs once. DOM nodes are created at mount time with reactive bindings that update surgically when state changes. No re-rendering, no virtual DOM.
  • Chunked-mask reconciliation. When state changes, the runtime computes a dirty set by reference-equality per tracked path, then gates each binding by a sparse mask — a binding whose mask doesn't intersect the dirty set is skipped without calling its accessor. Update cost scales with what changed, not with tree size, and there is no path ceiling.
  • Compiler optimization. The Vite plugin extracts each signal's dependency paths and lowers the common inline-view shape to allocation-free runtime calls. Zero runtime dependency-tracking overhead.

LLM integration

LLui provides first-class tooling for AI workflows:

  • llms.txt — concise framework reference for system prompts
  • llms-full.txt — comprehensive reference with all APIs, patterns, and rules (~515KB, full API surface)
  • @llui/agent — LLM-driven control surface: Claude reads state, enumerates actions, dispatches messages into the live app
  • @llui/mcp — MCP server exposing debug tools directly to LLMs via Model Context Protocol
  • @llui/compiler — compile-time error rules that catch common LLM mistakes at build time, not as lint warnings
  • Debugging — debug LLui apps interactively from the browser console or an MCP-connected LLM
  • Agents — drive any LLui-built app from a Claude conversation

Packages

Package Description
@llui/dom Runtime — component, mount, scope tree, bindings, structural primitives, element helpers
@llui/compiler Engine — signal TypeScript transform + compile-time lint rules (all error severity)
@llui/vite-plugin Vite adapter — wires the compiler into Vite, surfaces diagnostics via this.error()
@llui/compiler-introspection Opt-in compiler module — agent schemas, msg annotations, schema hash emission
@llui/compiler-devtools Opt-in compiler module — __componentMeta emission for source navigation
@llui/compiler-ssr Opt-in compiler module — 'use client' directive handling and SSR emission
@llui/effects Effect system — http, cancel, debounce, sequence, race, websocket, retry, upload
@llui/router Routing — structured path matching, history/hash mode, guards, link helper
@llui/transitions Animation helpers — transition(), fade, slide, scale, collapse, flip, spring
@llui/components 58 headless components + opt-in theme (CSS tokens, dark mode, Tailwind class helpers)
@llui/test Test harness — testComponent, testView, propertyTest, replayTrace
@llui/vike Vike SSR/SSG adapter — onRenderHtml, onRenderClient
@llui/mcp MCP server — LLM debug tools via Model Context Protocol
@llui/agent LLM control surface — LAP server + browser client; Claude drives the app in production
@llui/agent-bridge MCP bridge CLI (llui-agent) — translates Claude Desktop tool calls to LAP
@llui/devmode-annotate Dev-only HUD — annotate the running app into a shared on-disk notebook the LLM reads/writes
@llui/markdown Reactive Markdown rendering — markdown() parses to mdast, builds live reactive DOM, per-node renderer overrides, streaming-friendly keyed blocks
@llui/lexical Low-level Lexical ↔ signal-runtime binding — lexicalForeign, plugin contract, decorator bridge
@llui/lexical-collab Opt-in collaborative editing — yjsCollab over an injected Yjs provider: CRDT sync, scoped undo, presence
@llui/markdown-editor WYSIWYG Markdown editor — markdownEditor(), transformer registry, GFM/callout plugins, toolbar

Performance

Top-tier performance on js-framework-benchmark, competitive with Solid and Svelte. Full benchmarks →

Quick Start

mkdir my-app && cd my-app
npm init -y
npm install @llui/dom @llui/effects
npm install -D @llui/vite-plugin vite typescript
npx vite