API Reference
Every function exported by Chroma, with signatures and examples.
Everything is imported from the package generated by ./build.sh:
import {
e, Fragment, render,
hookState,
useEffect, useLayoutEffect,
useMemo, useCallback,
useRef, useReducer,
createContext, useContext,
useDebugValue,
Show, For,
cva, cx,
} from "./pkg/chroma.js";Core
e(tag, props, ...children)
Creates a node.
tagstring | Function— an HTML tag name, or a component function.propsobject | null— attributes, events, and special props.children— any number of children (see child semantics below).- Returns: a DOM
Element(string tag) or whatever the component returns. - Throws
chroma: e() expects a string tag or a component functioniftagis neither.
When tag is a string, an HTML element is created and each prop is applied:
| Prop shape | Behavior |
|---|---|
onClick, onInput, … (on* + function) | addEventListener("click" / "input" / …) |
ref (object) | Sets ref.current to the created element |
ref (function) | Called with the created element |
function (non-event, non-ref) | Reactive attribute — re-evaluated when its signals change |
false / null / undefined | Attribute omitted (or removed on update) |
| anything else | Static attribute, set once |
Prop-name aliases: className → class, htmlFor → for.
When tag is a function (component), it is invoked exactly once with props; children are merged into props.children (a single child stays unwrapped, multiple become an array).
Child semantics:
| Child | Behavior |
|---|---|
DOM Node | Appended as-is |
| function | Reactive child — mounted in a display: contents slot, re-evaluated when its signals change; may return text or DOM nodes |
| array | Flattened recursively |
null / undefined / boolean | Renders nothing |
| string / number / other | Text node (objects go through JSON.stringify) |
Fragment
Groups children without a wrapper element:
e(Fragment, null, e("h1", null, "Title"), e("p", null, "Body"))Returns a DocumentFragment.
render(component, container)
Runs component once and appends the resulting tree to container (a DOM Element). After mounting, all queued layout effects run synchronously — before the browser paints.
render(App, document.getElementById("app"));- Errors with
chroma: render() requires an Element as the container (did the selector return null?)ifcontaineris not anElement. - Errors with
chroma: the root component did not return a DOM nodeif the component's return value isn't a DOM node.
State
hookState(initial)
The primary state hook. Returns [getter, setter].
const [count, setCount] = hookState(0);
count(); // read current value (subscribes if inside a reactive expression)
setCount(5); // write; notifies only if the value changed (Object.is)
setCount(c => c+1); // updater form: receives the current value- The getter, called inside a reactive expression (reactive child, reactive attribute, effect, memo), registers a dependency on the signal.
- The setter re-runs only the bindings that depend on that signal. If the new value is
Object.is-equal to the old one, nothing is notified.
useReducer(reducer, initial)
Reducer-driven state. Returns [getter, dispatch].
const [state, dispatch] = useReducer((s, action) => {
switch (action.type) {
case "inc": return { n: s.n + 1 };
default: return s;
}
}, { n: 0 });
dispatch({ type: "inc" });dispatch(action) computes reducer(current, action) and writes the result to the underlying signal (same change-detection rules as hookState). If reducer throws, the error is logged to the console and the state is left unchanged.
Effects
Dependency arrays carry getters, not values: [count], not [count()]. See the Hooks Guide for the full semantics.
useEffect(fn, deps?)
Reactive side effect.
- First run is deferred to a microtask (after the component mounts).
- Re-runs are synchronous whenever a dependency changes.
- If
fnreturns a function, it is used as cleanup, invoked before each re-run.
useEffect(() => {
document.title = `count: ${count()}`;
return () => console.log("cleanup");
}, [count]);useLayoutEffect(fn, deps?)
Identical to useEffect, except the first run is synchronous: it fires as soon as render finishes mounting the tree, before paint. The DOM (and refs) are already available.
Derived values
useMemo(calc, deps?)
Cached derived value. Returns a reactive getter; the computation re-runs only when its dependencies change, not on every read.
const doubled = useMemo(() => count() * 2, [count]);
e("p", null, doubled); // reactive child bound to the memouseCallback(fn, deps?)
Identity function. Without re-renders, components run once and functions are never recreated — this hook exists purely for API compatibility with React-style code.
Refs
useRef(initial)
Non-reactive mutable reference: { current: initial }. Pass it as the ref prop of an element to receive the DOM node:
const input = useRef(null);
e("input", { ref: input });
useLayoutEffect(() => input.current.focus(), []);Context
createContext(defaultValue)
Creates a context object { Provider, defaultValue }.
Because there is no VDOM, children are normally evaluated before their parent. The Provider therefore receives its children as a function, so they can be evaluated with the context active:
const Theme = createContext("light");
e(Theme.Provider, { value: "dark" }, () => e(Child))useContext(ctx)
Returns the value of the nearest active Provider, or defaultValue if there is none.
function Child() {
const theme = useContext(Theme);
return e("p", null, `theme: ${theme}`);
}- Throws
chroma: useContext expects a context created with createContextifctxwasn't produced bycreateContext.
Debugging
useDebugValue(value, formatter?)
Debug label for custom hooks. Logs to the console (via console.debug) only if globalThis.__CHROMA_DEBUG__ is truthy. If formatter is provided, it is applied to value before logging.
globalThis.__CHROMA_DEBUG__ = true;
useDebugValue("useCounter");Control flow components
Show({ when, fallback?, children })
For({ each, children })
See Control Flow for Show and For in depth, including prop requirements and error messages.
Reusable components & variants
cva(config)
cx(...parts)
See Components & Variants for cva and cx in depth — the tools for reusing one e()-built component function across multiple looks (a Button with primary/ghost variants, a Badge with sizes) without hand-rolling class-string concatenation.