Skip to content

el()

The el() function creates element specs — blueprints for DOM elements with reactive props and children.

el(tag)
el(tag).props(props)
el(tag).props(props).ref(...callbacks)
el(tag)(children)
el(tag).props(props)(children)

tag : The element tag name. For the DOM adapter, this is any valid HTML tag ('div', 'button', 'input', etc.).

props (optional) : An object of attributes, properties, and event handlers. Values can be static or reactive.

callbacks (optional) : Lifecycle callbacks passed to .ref(). Called with the element when created, return a cleanup function for disposal.

children (optional) : Child content — strings, numbers, reactive values, or other element specs.

An ElementSpec for use with mount() or as a child of other elements.

el() is curried and chainable:

// Just a tag
el('div')
// With props
el('div').props({ className: 'container' })
// With children
el('div')('Hello')
// Full chain
el('button')
.props({ onclick: handleClick })
.ref(callback)
('Click me')

Elements are created once. Only individual props and text nodes update when their reactive values change:

const count = signal(0);
// This element is created once
// Only the text "Count: X" updates when count changes
const display = el('div')(
computed(() => `Count: ${count()}`)
);

Props can be static values or reactive (signals/computeds):

const isDisabled = signal(false);
const label = computed(() => isDisabled() ? 'Disabled' : 'Click me');
el('button').props({
className: 'btn', // static
disabled: isDisabled, // reactive
onclick: () => doSomething() // event handler
})(label) // reactive child

The final call accepts any number of children:

el('div')(
'Text content',
el('span')('Nested element'),
computed(() => `Dynamic: ${value()}`),
someCondition && el('p')('Conditional')
)

Children can be:

  • Strings and numbers (rendered as text)
  • Reactive values (signals, computeds)
  • Other element specs
  • null or undefined (ignored)
  • Arrays of any of the above
// Empty div
el('div')()
// Div with text
el('div')('Hello, world')
// Nested structure
el('article')(
el('h1')('Title'),
el('p')('First paragraph'),
el('p')('Second paragraph')
)
el('input').props({
type: 'text',
placeholder: 'Enter name...',
value: name,
oninput: (e) => name(e.target.value)
})()
el('a').props({
href: 'https://example.com',
target: '_blank'
})('Visit site')
const theme = signal<'light' | 'dark'>('light');
const count = signal(0);
el('div').props({
className: computed(() => `app theme-${theme()}`),
})(
el('p')(computed(() => `Count: ${count()}`)),
el('button').props({
onclick: () => count(count() + 1)
})('Increment')
)
el('input')
.props({ type: 'text' })
.ref(
(input) => input.focus(),
(input) => {
// Setup
const handler = () => console.log('focused');
input.addEventListener('focus', handler);
// Cleanup
return () => input.removeEventListener('focus', handler);
}
)()

Pre-bind commonly used tags:

const div = el('div');
const button = el('button');
const input = el('input');
// Use without repeating tag names
div.props({ className: 'container' })(
input.props({ type: 'text' })(),
button.props({ type: 'submit' })('Submit')
)
  • map() — Render lists of elements
  • match() — Conditional element rendering
  • mount() — Attach element specs to the DOM
  • Adding a UI — Tutorial on building UIs