API Reference
Core Functions
These are the fundamental functions you'll use in every nuclo application.
update()
update(): void
Triggers a synchronous update of all reactive elements in the DOM. Call this function after mutating state to refresh the UI.
Usage
let count = 0;
button('Increment', on('click', () => {
count++;
update(); // Trigger UI update
}));
Notes
- Updates are synchronous and happen immediately
- Only reactive functions (zero-arg functions) are re-evaluated
- Batch multiple state changes before calling
update()
for better performance - You can call
update()
as many times as needed, but batching is more efficient
render()
render(element: Element | Node, container: Element): void
Appends an element to a container in the DOM. This is typically used once at application startup.
Parameters
- element: The element to render (created with tag builders like
div()
) - container: The DOM element to append to (e.g.,
document.body
)
Usage
const app = div(
h1('My App'),
p('Hello, world!')
);
render(app, document.body);
Notes
- The element is appended to the container (not replaced)
- You can call
render()
multiple times to mount different elements - Common pattern: render a single root element that contains your entire app
list()
list<T>(provider: () => T[], renderer: (item: T, index: number) => Node): DocumentFragment
Synchronizes an array to a list of DOM elements. Elements are reused when the same object reference appears in the array.
Parameters
- provider: A function that returns the current array
- renderer: A function that creates a DOM element for each item
Usage
let todos = [
{ id: 1, text: 'Learn nuclo', done: false },
{ id: 2, text: 'Build app', done: false }
];
list(() => todos, (todo, index) =>
div(
{ className: () => todo.done ? 'done' : '' },
span(() => `${index + 1}. ${todo.text}`),
button('Toggle', on('click', () => {
todo.done = !todo.done;
update();
}))
)
);
How It Works
- Tracks items by object identity (reference equality)
- When you call
update()
, the list provider is called - nuclo compares the new array to the previous one
- Elements are reused for items that are the same reference
- New elements are created for new items
- Elements are removed for items no longer in the array
- Elements are reordered if items moved position
Important Notes
Object Identity Matters!
// ✓ Good: Mutate the object
todos[0].done = true;
update();
// ✗ Bad: Creates a new object, element will be recreated
todos[0] = { ...todos[0], done: true };
update();
// ✓ Good: Mutate the array
todos.push(newTodo);
todos.sort((a, b) => a.id - b.id);
update();
// ✓ Also good: Reassign with filtered array
todos = todos.filter(t => !t.done);
update();
Advanced Usage
// Nested lists
list(() => categories, category =>
div(
h3(category.name),
list(() => category.items, item =>
div(item.name)
)
)
);
// Filtered lists
list(() => todos.filter(t => !t.done), todo =>
div(todo.text)
);
when()
when(condition: () => boolean, ...content: Renderable[]): ConditionalBuilder
Conditionally renders content based on a boolean condition. Supports chaining with .when()
and .else()
.
Parameters
- condition: A function that returns a boolean
- content: Elements to render when the condition is true
Basic Usage
let isLoggedIn = false;
when(() => isLoggedIn,
div('Welcome back!')
).else(
div('Please log in')
);
Chaining Conditions
let role = 'user'; // 'admin' | 'user' | 'guest'
when(() => role === 'admin',
div('Admin Panel')
).when(() => role === 'user',
div('User Dashboard')
).else(
div('Guest View')
);
Multiple Elements
when(() => showDetails,
h2('Details'),
p('Here are the details...'),
button('Close', on('click', () => {
showDetails = false;
update();
}))
);
How It Works
- Evaluates conditions in order until one returns
true
- Renders the content for the first matching condition
- If no conditions match and
.else()
is provided, renders else content - DOM elements are preserved if the active branch doesn't change
- Elements are only created/destroyed when switching between branches
Performance Benefits
// Elements persist across updates if the same branch is active
let count = 0;
when(() => count > 0,
div('Count is positive') // This div stays alive while count > 0
).else(
div('Count is zero or negative')
);
// Multiple updates with count > 0 won't recreate the div
count = 5; update();
count = 10; update();
count = 15; update(); // Same div element throughout
Nested Conditionals
when(() => user.isAuthenticated,
when(() => user.hasPermission,
div('Protected Content')
).else(
div('Access Denied')
)
).else(
div('Please log in')
);
on()
on<K extends keyof HTMLElementEventMap>(
event: K,
handler: (event: HTMLElementEventMap[K]) => void,
options?: AddEventListenerOptions
): Modifier
Attaches an event listener to an element. Returns a modifier that can be passed to tag builders.
Parameters
- event: The event name (e.g., 'click', 'input', 'keydown')
- handler: The event handler function
- options: Optional event listener options (passive, capture, once)
Basic Usage
button('Click me',
on('click', (e) => {
console.log('Button clicked!', e);
})
);
Multiple Events
input(
on('input', (e) => {
value = e.target.value;
update();
}),
on('focus', () => {
isFocused = true;
update();
}),
on('blur', () => {
isFocused = false;
update();
})
);
Event Options
// Passive event for better scroll performance
div(
on('scroll', handleScroll, { passive: true })
);
// Capture phase
div(
on('click', handleClick, { capture: true })
);
// One-time event
button('Click once',
on('click', handleClick, { once: true })
);
Keyboard Events
input(
on('keydown', (e) => {
if (e.key === 'Enter') {
handleSubmit();
} else if (e.key === 'Escape') {
handleCancel();
}
})
);
Form Events
form(
on('submit', (e) => {
e.preventDefault();
handleFormSubmit();
}),
input({ type: 'text', name: 'username' },
on('input', (e) => {
username = e.target.value;
update();
})
),
button({ type: 'submit' }, 'Submit')
);
Tag Builders
nuclo provides builder functions for all HTML and SVG elements. Each function creates and returns a DOM element.
Signature
tagName(...children: Renderable[]): HTMLElement
tagName(attributes: Attributes, ...children: Renderable[]): HTMLElement
Basic Usage
// Just children
div('Hello, world!');
// Attributes + children
div(
{ className: 'container', id: 'main' },
'Hello, world!'
);
// Nested elements
div(
h1('Title'),
p('Paragraph'),
ul(
li('Item 1'),
li('Item 2')
)
);
Attributes
Attributes are passed as an object. They can be static values or reactive functions.
Static Attributes
div({
id: 'main',
className: 'container',
'data-test': 'value',
'aria-label': 'Main content'
});
Reactive Attributes
div({
className: () => isActive ? 'active' : 'inactive',
'aria-pressed': () => isActive,
disabled: () => !isValid,
hidden: () => !isVisible
});
Style Attributes
// Object style
div({
style: {
color: 'red',
fontSize: '16px',
backgroundColor: () => isActive ? 'blue' : 'gray'
}
});
// String style
div({
style: 'color: red; font-size: 16px;'
});
// Reactive string style
div({
style: () => `color: ${color}; font-size: ${size}px;`
});
Boolean Attributes
input({
type: 'checkbox',
checked: () => isChecked,
disabled: () => isDisabled,
required: true,
readonly: false
});
Special Attributes
// className (maps to 'class')
div({ className: 'my-class' });
// htmlFor (maps to 'for')
label({ htmlFor: 'input-id' }, 'Label');
// Data attributes
div({ 'data-id': '123', 'data-type': 'user' });
Modifiers
Modifiers are special objects that can be passed to tag builders to perform additional operations on elements.
Event Modifiers
Created with the on()
function:
button('Click',
on('click', handleClick),
on('mouseenter', handleHover)
);
Custom Modifiers
You can create custom modifiers for reusable element logic:
// Example: Focus modifier
function focus() {
return {
__modifier: true,
apply(element) {
requestAnimationFrame(() => element.focus());
}
};
}
// Usage
input(focus());
Type Definitions
All functions are fully typed with TypeScript. Add "types": ["nuclo/types"]
to your tsconfig.json to get global type definitions.
For detailed type information, see the types directory in the repository.