Skip to main content
Every Solar component is a first-class contract, not just a function. You define a component by calling defineComponent() with three required fields — a name, a props schema, and a render function — and Solar turns that config into a callable, self-validating component function. This explicit structure is what lets Solar validate props at runtime, power the component registry, and give AI agents a reliable catalog to reason about before writing any composition code.

Anatomy of a component

The example below shows a complete Button component. It covers everything you need to write a production-ready Solar component: typed and validated props, a default value, an enum constraint, and registration with the registry.
import { defineComponent, createElement, registry } 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)
  },
})

registry.register(Button)
export default Button
Solar validates every prop the moment Button(...) is called. If a prop fails validation, you get a structured ContractError immediately — before render ever runs.

The config object

defineComponent() takes a single config object with three keys. name (required string) The component’s display name. Solar uses this in error messages, the component registry, and the _source tag it stamps on every vnode the component returns. It must exactly match the filename and the default export name. props (object) A map of prop names to schema descriptors. Each descriptor controls the type check, required constraint, default value, and allowed enum values for that prop. See the Contracts page for the full list of descriptor fields. render (required function) Receives the fully resolved and validated props object, then returns a vnode built with createElement(). Solar calls render only after all props have passed validation, so you can rely on the types inside this function.

File structure rules

Solar enforces a rigid set of file conventions. These rules exist so that AI agents can navigate and generate component files without needing to infer structure from context.
1

One component per file

Each .js file in the components/ directory defines exactly one component. Never export multiple components from a single file.
2

Filename matches the component name

If your component’s name is "UserCard", the file must be UserCard.js. The registry, the slot validator, and the error formatter all depend on this 1:1 mapping.
3

Default export is the defineComponent call

Assign the result of defineComponent() to a const, then export default that const. Do not export the raw config object or a wrapper function.
4

Register immediately after defining

Call registry.register(Button) (or your component name) on the line after defineComponent(), before the default export. This ensures the component is available in the registry the moment its file is imported.

createElement

Inside render, you build your vnode tree with createElement(type, props, ...children).
createElement('button', { class: 'btn', onclick: handleClick }, 'Save')
The three arguments are:
  • type — a string tag name ('div', 'button', 'h2', etc.) or another component function
  • props — an object of DOM attributes and event handlers; pass {} if you have none
  • ...children — zero or more child vnodes or strings
You can nest calls to build a tree:
render({ title, body }) {
  return createElement('div', { class: 'card' },
    createElement('h3', {}, title),
    createElement('p',  {}, body),
  )
}
createElement returns a plain vnode object. Solar’s renderer walks the vnode tree and mounts it to the DOM.
Components are called as plain functions, not with JSX or a template compiler. To use your Button component, call it directly: Button({ label: 'Save', onClick: handleSave }). The return value is a vnode you can pass as a child to createElement or as a slot prop to another component.
Looking for a full end-to-end example — from file setup to mounting in the browser? See the Quickstart guide for a step-by-step walkthrough.