Skip to content

LLM UI Spec

Preview: This specification is under active development and may change.

A declarative XML specification for defining UI structure that can be interpreted by LLMs to generate platform-specific code.

Platform-Specific Documentation

For platform-specific types and patterns, see:

  • Webapp - Web applications (pages, modals, routing)
  • Mobile - Mobile applications (screens, sheets, gestures)
  • Desktop - Desktop applications (windows, menus, toolbars)

This document covers core concepts shared across all platforms.


Overview

LLM UI Spec uses two primitives to define all UI:

PrimitivePurposeExamples
<element>Leaf nodes (no children)button, text, input, image, icon
<container>Nodes with childrenrow, column, grid, accordion, tabs, modal

Human Readability

LLM UI Spec is designed to be read and written by humans. The core principle is: write what you mean, not what the system requires.

Natural Language First

Describe UI behavior naturally:

xml
<!-- Instead of verbose attributes -->
<element type="button" variant="primary" size="lg" disabled="false" />

<!-- Write what you mean -->
<element type="button" prompt="large primary button">Save</element>

Minimal Syntax

Only specify what's necessary:

xml
<!-- Type from context -->
<container type="menu">
  <element>Edit</element>        <!-- Obviously a menu item -->
  <element>Delete</element>      <!-- Obviously a menu item -->
</container>

<!-- Type explicit when needed -->
<element type="button">Save</element>

Prompt-Driven Patterns

Common patterns expressed naturally:

xml
<!-- Styling -->
prompt="style primary"
prompt="large heading"
prompt="muted text"

<!-- Layout -->
prompt="gap medium"
prompt="padding large"
prompt="3 columns"

<!-- Behavior -->
prompt="on click @action.save"
prompt="on submit @action.create"
prompt="if @state.loading"

<!-- Combined -->
prompt="large primary button, on click @action.save"

Common Attributes

All primitives share these attributes:

AttributeTypeDescription
namestringIdentifier for referencing
typestringElement or container type
promptstringNatural language behavior description
usereferenceData binding
ifexpressionRender only if true
if-notexpressionRender only if false
for-eachcollectionIterate over items

Attribute Expression

All attributes (except name) can be expressed in prompt:

xml
<!-- Attribute syntax -->
<element type="text" use="@user.name" />
<element type="spinner" if="@state.loading" />
<container type="card" for-each="@state.items" />

<!-- Prompt syntax (equivalent) -->
<element type="text" prompt="use @user.name" />
<element type="spinner" prompt="if @state.loading" />
<container type="card" prompt="for each @state.items" />

<!-- Type in prompt -->
<element prompt="text, use @user.name" />
<element prompt="spinner, if @state.loading" />
<container prompt="card, for each @state.items" />

Type Inference

Type is optional when parent context makes it clear:

xml
<!-- Inside accordion, children are accordion content -->
<container type="accordion">
  <container prompt="label Section 1">Content 1</container>
  <container prompt="label Section 2">Content 2</container>
</container>

<!-- Inside tabs, children are tab content -->
<container type="tabs">
  <container prompt="label Overview">Overview</container>
  <container prompt="label Settings">Settings</container>
</container>

<!-- Inside list, children are list items -->
<container type="list">
  <container prompt="chevron">Item 1</container>
  <container prompt="chevron">Item 2</container>
</container>

Element

Leaf nodes that display content or accept input. Cannot have children.

xml
<element type="text" prompt="large heading">Welcome</element>
<element type="button" prompt="style primary, on click @action.save">Save</element>
<element type="input" prompt="required, placeholder Enter email">Email</element>
<element type="image" use="@user.avatar" prompt="avatar style, size 40" />
<element type="icon" prompt="home, size large" />
<element type="badge" use="@item.status" prompt="style success" />

Common Element Types

TypeDescription
textText display
buttonClickable button
inputText input
imageImage display
iconIcon display
badgeStatus badge
linkNavigation link
separatorVisual divider
spinnerLoading indicator

See platform specs for additional element types.


Container

Nodes that hold child elements or other containers.

xml
<container type="column" prompt="padding medium, gap large">
  <element type="text" prompt="heading">Title</element>
  <element type="text" prompt="muted" use="@item.description" />
</container>

<container type="row" prompt="justify between, align center">
  <element type="text">Label</element>
  <element type="button" prompt="style primary">Action</element>
</container>

Common Container Types

TypeDescription
rowHorizontal layout
columnVertical layout
gridGrid layout
cardCard with border/shadow
formForm container
sectionSemantic section
scrollScrollable container

See platform specs for additional container types.


Data Binding

Use the use attribute for data binding:

xml
<!-- Display data -->
<element type="text" use="@user.name" />
<element type="image" use="@user.avatar" />
<element type="badge" use="@item.status" />

<!-- Also expressible in prompt -->
<element type="text" prompt="use @user.name" />

Control Flow

Conditional Rendering

xml
<!-- Show if condition is true -->
<element type="spinner" if="@state.loading" />

<!-- Show if condition is false -->
<element type="text" if-not="@state.loading">Loaded</element>

<!-- Conditional container -->
<container type="card" if="@state.hasData">
  <element use="@state.data.title" />
</container>

Loops

xml
<!-- Iterate over collection (item available as @item) -->
<container type="card" for-each="@state.items">
  <element use="@item.name" />
</container>

<!-- In prompt -->
<container type="card" prompt="for each @state.items">
  <element use="@item.name" />
</container>

Prompt-Driven Development

Use the prompt attribute to describe behavior in natural language:

xml
<!-- Styling -->
<element type="button" prompt="style primary, large size">Save</element>
<container type="row" prompt="gap medium, justify between" />

<!-- Behavior -->
<element type="button" prompt="on click @action.save, show loading while saving">Save</element>
<container type="form" prompt="on submit @action.create, on success close modal" />

<!-- Complex descriptions -->
<container type="data-table" use="@state.users" prompt="
  sortable, filterable, 25 per page,
  row selection, bulk delete action
" />

Prompt as Child

For complex prompts, use a child element:

xml
<container type="column">
  <prompt context="true">
    Use minimal design with monochrome colors.
    All children should follow this style.
  </prompt>
  <container type="card" prompt="User profile" />
  <container type="card" prompt="Activity feed" />
</container>

Reference Namespaces

All references use the @ prefix:

NamespaceSyntaxDescription
@entity@entity.NameEntity schema
@component@component.NameComponent reference
@state@state.namePage/component state
@item@item.fieldCurrent loop item
@action@action.nameDefined action
@guard@guard.nameGuard reference
@page@page.NamePage navigation (webapp)
@screen@screen.NameScreen navigation (mobile)
@modal@modal.NameModal reference (webapp)
@sheet@sheet.NameSheet reference (mobile)
@layout@layout.NameLayout reference
@prop@prop.nameComponent prop
@param@param.nameURL/route parameters
@theme@theme.type.nameTheme value (e.g., @theme.color.primary)

Entities

Define data models. Shared across all platforms.

xml
<entities>
  <entity name="User">
    <field name="id" type="uuid" />
    <field name="name" type="string" prompt="required" />
    <field name="email" type="email" prompt="required" />
    <field name="avatar" type="image" />
    <field name="posts" type="ref" prompt="references @entity.Post, many" />
  </entity>

  <entity name="Post">
    <field name="id" type="uuid" />
    <field name="title" type="string" prompt="required, max 100 chars" />
    <field name="content" type="richtext" />
    <field name="author" type="ref" prompt="references @entity.User" />
  </entity>
</entities>

Field Types

TypeDescription
uuidUnique identifier
stringText value
textLong text
richtextRich/formatted text
emailEmail address
urlURL
phonePhone number
numberNumeric value
integerWhole number
decimalDecimal number
booleanTrue/false
dateDate only
timeTime only
datetimeDate and time
imageImage reference
fileFile reference
jsonJSON object
enumEnumerated values
refReference to another entity

Components

Reusable UI compositions with props.

xml
<components>
  <component name="UserCard">
    <props>
      <prop name="user" type="@entity.User" prompt="required" />
      <prop name="compact" type="boolean" prompt="default false" />
    </props>

    <container type="row" prompt="gap small">
      <element type="image" use="@prop.user.avatar" prompt="avatar style" />
      <container type="column">
        <element use="@prop.user.name" />
        <element prompt="muted" use="@prop.user.email" if-not="@prop.compact" />
      </container>
    </container>
  </component>
</components>

Using Components

xml
<!-- Reference with type -->
<element type="@component.UserCard" use="@state.currentUser" />

<!-- With for-each (item passed automatically) -->
<element type="@component.UserCard" for-each="@state.users" />

Actions

Reusable action definitions. Referenced with @action.name.

xml
<actions>
  <action name="save" prompt="submit form, show success toast" />
  <action name="delete" prompt="confirm dialog, delete item, refresh list" />
  <action name="export" prompt="download as CSV file" />
</actions>

Using Actions

xml
<element type="button" prompt="on click @action.save">Save</element>
<container type="form" prompt="on submit @action.save, on success close modal" />

Guards

Authentication and authorization guards. Referenced with @guard.name.

xml
<guards>
  <guard name="auth" prompt="if not authenticated, redirect to login" />
  <guard name="admin" prompt="if not admin role, redirect to unauthorized" />
</guards>

Using Guards

xml
<!-- Webapp -->
<page name="Dashboard" route="/dashboard">
  <guards>
    <guard type="@guard.auth" />
  </guards>
</page>

<!-- Mobile -->
<screen name="Dashboard">
  <guards>
    <guard type="@guard.auth" />
  </guards>
</screen>

<!-- Desktop -->
<view name="Editor">
  <guards>
    <guard type="@guard.auth" />
  </guards>
</view>

File Organization

File Extensions

PlatformFile Pattern
Shared<app>.shared.spec.xml
Webapp<app>.webapp.spec.xml or <app>.spec.xml
Mobile<app>.mobile.spec.xml
Desktop<app>.desktop.spec.xml

Import

xml
<import src="./myapp.shared.spec.xml" />

Example Structure

myapp/
├── myapp.shared.spec.xml   # Entities, components, actions, guards
├── myapp.spec.xml          # Web pages, modals
├── myapp.mobile.spec.xml   # Mobile screens, sheets
└── myapp.desktop.spec.xml  # Desktop windows, menus

Config

Theme and application configuration.

xml
<config>
  <theme>
    <value type="color" name="primary">#3B82F6</value>
    <value type="color" name="danger">#EF4444</value>
    <value type="spacing" name="unit">4px</value>
    <value type="radius" name="default">8px</value>
  </theme>

  <llm>
    <prompt type="global">
      Generate accessible, production-ready code.
      Use semantic HTML and proper ARIA labels.
    </prompt>
  </llm>
</config>

Referencing Theme Values

Use @theme.type.name to reference theme values:

xml
<element type="button" prompt="background @theme.color.primary">Submit</element>
<container prompt="padding @theme.spacing.unit, rounded @theme.radius.default">
  <element prompt="text color @theme.color.danger">Error message</element>
</container>

Best Practices

  1. Use prompts for styling - Describe visual appearance naturally
  2. Use prompts for behavior - Describe interactions with "on click", "on submit", etc.
  3. Omit types when context is clear - Inside menus, lists, tabs, etc.
  4. Define reusable elements - Put entities, components, actions, guards in shared spec
  5. Reference with @ - Use reference namespaces for linking

License

MIT

Released under the MIT License.