Skip to main content
Solar is purpose-built for apps where an AI agent writes the UI. Internal tools are the strongest fit — they get regenerated frequently as requirements change, conventions must be enforced automatically rather than reviewed by hand, and prop contracts need to catch type mismatches at the component boundary before they surface as runtime errors ten layers deep.

Why Solar for internal tools

  • Components are regenerated frequently. Business logic shifts, dashboards get new columns, approval flows change. Solar’s rigid file structure — one component per file, default export always a defineComponent call — means a new generation never breaks existing conventions. The agent has a rulebook it can follow without inference.
  • The registry gives agents the full catalog before they write a single line. Call registry.manifest() and you get a machine-readable JSON snapshot of every registered component, its props, their types, required flags, enums, and defaults. The agent targets this exact contract instead of guessing.
  • ContractError catches mistakes at the boundary. Pass the wrong prop type and Solar throws a structured, parseable error immediately — not a cryptic DOM exception at render time. The agent can read err.toJSON() and self-correct in the next attempt.

The workflow

1

Build your base component library

Define the primitives your internal tools will share — Button, Table, Form, Badge — using defineComponent. Each component declares its prop schema explicitly.
import { defineComponent, createElement } from './solar/index.js'

const Button = defineComponent({
  name: 'Button',
  props: {
    label: { type: 'string', required: true },
    onClick: { type: 'function', required: true },
    variant: { type: 'string', enum: ['primary', 'secondary'], default: 'primary' },
  },
  render({ label, onClick, variant }) {
    return createElement('button', { class: variant, onclick: onClick }, label)
  },
})
2

Register all components

Call registry.register() for each component so Solar can track it and include it in the manifest. A self-registering pattern keeps this automatic — call registry.register(Button) at the bottom of each component file.
import { registry } from './solar/index.js'

registry.register(Button)
export default Button
3

Export the manifest for your agent

Call registry.manifest() and paste the result into your agent’s system prompt or context window. The manifest is a JSON string listing every component’s name and full prop schema — the agent uses this to know exactly what it can compose and what each prop expects.
const manifest = registry.manifest()
// Paste this into your agent context:
// [
//   {
//     "name": "Button",
//     "props": {
//       "label": { "type": "string", "required": true },
//       "onClick": { "type": "function", "required": true },
//       "variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" }
//     }
//   },
//   ...
// ]
4

Prompt your agent

Give the agent the manifest and a task in plain language. Because the agent has an exact prop contract for every available component, it generates code that targets a known schema rather than guessing at conventions.
“Build an overdue invoices view with an Approve button. Use the components in the manifest.”
5

Mount the generated component

Take the component the agent returns and mount it to the DOM with mountComponent(). Solar validates props at mount time — if the agent made a mistake, you get a structured ContractError before anything renders.
import { mountComponent } from './solar/index.js'
import InvoiceView from './components/InvoiceView.js'

mountComponent(InvoiceView, { invoices: data }, document.getElementById('app'))

Example: agent-generated invoice component

Here is the kind of component an agent produces when it has the Solar manifest and a clear task. Every prop is explicitly typed, the render function uses only createElement, and the Button component is composed by name — exactly the contract Solar enforces.
import { defineComponent, createElement, registry } from './solar/index.js'
import Button from './components/Button.js'

const InvoiceRow = defineComponent({
  name: 'InvoiceRow',
  props: {
    invoice: { type: 'object', required: true },
    onApprove: { type: 'function', required: true },
  },
  render({ invoice, onApprove }) {
    return createElement('tr', {},
      createElement('td', {}, invoice.id),
      createElement('td', {}, invoice.amount),
      createElement('td', {},
        Button({ label: 'Approve', onClick: () => onApprove(invoice.id) }),
      ),
    )
  },
})

registry.register(InvoiceRow)
export default InvoiceRow
The agent didn’t need to infer how Button works — it read the contract from the manifest and called it with exactly the right props.
Because Solar is runtime-based, there is no compile step between generation and execution. The agent generates a component, you load it in the browser, and it runs immediately. The generation-to-render loop is as tight as it can be — no waiting for a bundler.

Next steps

AI Generation Guide

Step-by-step guide to generating Solar components with AI agents