Conditional Rendering
Whisq has two primitives for conditional rendering:
when()— for a single two-branch decision: show this, or show that.match()— for three or more branches picked by predicate.
Function children (a bare () => ... returning a node or null) remain available for inline one-offs, but for the common three-way shapes (loading / error / data, idle / invalid / valid, empty / filtered-empty / list), reach for match().
when() — two-branch conditional
Section titled “when() — two-branch conditional”import { signal, when, div, p, button } from "@whisq/core";
const loggedIn = signal(false);
div( when(() => loggedIn.value, () => p("Welcome back!"), () => button({ onclick: login }, "Sign In"), ),);The third argument (otherwise) is optional — omit it to render nothing on the false branch:
when(() => showBanner.value, () => div({ class: "banner" }, "Notice!"),);match() — three or more branches
Section titled “match() — three or more branches”match() evaluates branches in order and renders the first whose predicate returns truthy. An optional trailing fallback (a bare render function, not wrapped in a tuple) renders when no branch matches. Re-evaluates reactively like any other child.
import { match, resource, div, p, button, ul, each, li } from "@whisq/core";
const users = resource(() => fetch("/api/users").then((r) => r.json()));
div( match( [() => users.loading(), () => p("Loading…")], [() => !!users.error(), () => div( p(() => `Error: ${users.error()!.message}`), button({ onclick: () => users.refetch() }, "Retry"), )], [() => !!users.data(), () => ul(each(() => users.data()!, (u) => li(u.name)))], () => p("No data yet."), // fallback ),);The loading/error/data tri-state on a resource() is the canonical case — it’s what match() was added for, and it’s the pattern /guides/data-fetching/ leads with. Any time you find yourself chaining three or more when()s against states of the same thing, reach for match().
First-true-wins
Section titled “First-true-wins”If two predicates are both true at the same time, only the earlier branch renders:
match( [() => count.value > 10, () => p("More than ten")], [() => count.value > 0, () => p("Some")], // skipped when count > 10 () => p("None"),);Order your branches from most-specific to most-general.
Fallback
Section titled “Fallback”A trailing bare render function (no tuple) fires when no predicate matches:
match( [() => status.value === "ok", () => p("All good")], [() => status.value === "warn", () => p({ class: "warn" }, "Heads up")], () => p("Unknown status"), // fires when status is anything else);Omit the fallback to render nothing when no branch matches — like when() without its otherwise.
Inline — function child
Section titled “Inline — function child”For a one-off inline branch where a named variable or a full when() call would be overkill, a bare function child returning a node or null is still idiomatic:
div( () => isError.value ? p({ class: "error" }, "Something broke") : null,);Use this when the predicate is trivial and the rendered branch is a one-liner. For anything beyond that — especially three or more outcomes — use match().
Rendering nothing
Section titled “Rendering nothing”Return null from any render function to render nothing:
div( () => showMessage.value ? p("Visible!") : null,);Both when() and match() already handle this for you — if the matching branch returns null, nothing is rendered.
Next Steps
Section titled “Next Steps”- List Rendering —
each()for arrays, with keyed reconciliation. - Data Fetching —
resource()and the canonicalmatch()tri-state. - API — when() — reference.