Skip to content

Define a component with a setup function.

function component<P extends Record<string, any>>(
setup: (props: P) => ComponentSetupReturn,
): ComponentDef<P>
// Since alpha.9 — setup can return a WhisqNode OR a function child
type ComponentSetupReturn = WhisqNode | (() => unknown);
ParamTypeDescription
setup(props: P) => ComponentSetupReturnSetup function — runs once. Returns either a WhisqNode (element-shaped — the canonical form) or a () => unknown function child (since alpha.9 — for branching components).

ComponentDef<P> — a callable that accepts props and returns a WhisqNode.

import { signal, component, div, button, span, mount } from "@whisq/core";
const Counter = component((props: { initial?: number }) => {
const count = signal(props.initial ?? 0);
return div(
button({ onclick: () => count.value-- }, "-"),
span(() => ` ${count.value} `),
button({ onclick: () => count.value++ }, "+"),
);
});
mount(Counter({ initial: 10 }), document.getElementById("app")!);

When a component’s entire job is to branch — render loading / error / data, or swap between views — setup can return the function directly. No wrapper element required.

import { component, match, p } from "@whisq/core";
const Screen = component(() =>
match(
[() => view.value === "loading", () => p("loading")],
[() => view.value === "data", () => DataView({})],
() => p("empty"),
),
);

Works for any zero-arg function return — when(), match(), each() result, or a plain () => someNode. Under the hood, component() wraps the function in a fragment bounded by start/end markers (the same pattern errorBoundary and keyed each already use). Branch switches dispose the previous WhisqNode before inserting the next — no node leaks.

Pick the shape by what the component is:

  • Element root (div, section, custom semantic element) — use when you need to attach class / style / events to the root. This is the canonical form.
  • Function-child root — use when the component is the branch. No wrapper DOM, no sacrificial div.

onMount / onCleanup hooks registered inside setup fire normally in both shapes.

Before alpha.9, the function-child form threw WhisqStructureError at mount; you had to wrap in a neutral element. See the Using match() as a component root section for migration notes.

Docs current to v0.1.0-alpha.9 . All releases →