Skip to main content
No-code and low-code platforms need a generation layer they can trust. Solar gives your platform a strict, machine-readable schema for every component — the generation layer always knows exactly what a valid Button, Form, or Table looks like before it tries to render one. You never have to hardcode what a component accepts; you read it directly from the registry.

The schema-first advantage

Every component you define with defineComponent carries its full prop schema as component._schema. Solar’s registry aggregates these into a single queryable catalog. Call registry.manifest() at any point and you get a complete JSON description of every registered component — prop names, types, required flags, enums, and defaults. Your platform can use this to render a form-based configurator dynamically, without hardcoding a single prop name. When you add a new component to your library, the configurator picks it up automatically on the next manifest() call.
import { registry } from './solar/index.js'

const manifest = registry.manifest()
// Returns a JSON string like:
// [
//   {
//     "name": "Button",
//     "props": {
//       "label": { "type": "string", "required": true },
//       "onClick": { "type": "function", "required": true },
//       "variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" }
//     }
//   },
//   ...
// ]

Key features for builders

Machine-Readable Schema

registry.manifest() returns the full prop schema for every registered component as structured JSON. Your platform reads the schema at runtime — no manual sync between component code and your configuration UI.

Typed Slot Composition

The slot system lets your platform compose components predictably. A parent declares which component type can fill a slot — passing anything else throws a ContractError before it reaches the DOM.

Runtime Validation

ContractError fires at the component boundary the moment a prop contract is violated. Your platform catches the error before a misconfigured component ever renders, and surfaces the fix message directly to the user.

No Compiler

Solar is runtime-based. Generated components run immediately in the browser without a build step. Your platform can go from user configuration to live preview in a single frame.

Building a config-driven UI

When a user fills out a configuration form in your platform, you get back a plain JSON object describing which component to render and what props to pass. Use registry.get() to resolve the component by name, then mount it with mountComponent().
import { registry, mountComponent } from './solar/index.js'

// User fills out a form in your platform's UI; your platform gets:
const config = {
  component: 'Button',
  props: { label: 'Submit', variant: 'primary', onClick: handleSubmit },
}

// Resolve the component by name and mount it:
const component = registry.get(config.component)
mountComponent(component, config.props, document.getElementById('output'))
The config is fully serializable — you can store it, diff it, and replay it. Swapping the component is as simple as changing config.component and remounting.

Introspecting component schemas

Use registry.list() to build a dynamic component picker. The method returns an array of objects — each with a name and a props map — that your platform can use to render both a picker UI and a per-prop editor without any hardcoded component knowledge.
import { registry } from './solar/index.js'

const components = registry.list()
// → [
//     { name: 'Button', props: { label: { type: 'string', required: true }, ... } },
//     { name: 'Table',  props: { rows: { type: 'array', required: true }, ... } },
//     ...
//   ]

// Build a component picker:
const names = components.map(c => c.name)
// → ['Button', 'Table', ...]

// Build a prop editor for the selected component:
const selected = components.find(c => c.name === 'Button')
const propEntries = Object.entries(selected.props)
// → [['label', { type: 'string', required: true }], ...]
// Render a text input for 'string', a toggle for 'boolean', a select for 'enum', etc.
Every time your component library grows, registry.list() reflects the change automatically — no configuration files to update.
When a user submits a misconfigured component, catch ContractError and surface its fix property directly in your platform’s UI. The fix string is a human-readable instruction like "Pass a string value for \"label\"" — show it inline next to the offending field so users can correct their input without leaving the builder.
try {
  mountComponent(component, config.props, container)
} catch (err) {
  if (err.name === 'ContractError') {
    showFieldError(err.prop, err.fix)
  }
}