Skip to content

Quick Start

Build a working Whisq app in under 5 minutes.

Terminal window
npm create whisq@latest my-app
cd my-app
npm install
npm run dev

Open http://localhost:5173 — you should see a counter app.

Open src/main.ts:

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

Let’s break this down:

const count = signal(0); // create a reactive value
count.value++; // write — triggers updates everywhere
div({ class: "counter" }, // props object (optional)
h1("Whisq Counter"), // text child
button({ onclick: fn }), // event handler
span(() => count.value), // reactive child (function)
)

Every HTML tag is a function: div(), span(), button(), input(), h1(), etc.

const Counter = component((props) => {
// setup code runs once
const count = signal(0);
// return a WhisqNode
return div(/* ... */);
});
// use it — it's a function call
Counter({ initial: 10 })

Try editing src/main.ts:

// Change the title
h1("My First Whisq App"),
// Add a computed display
const doubled = computed(() => count.value * 2);
// Show it
p(() => `Doubled: ${doubled.value}`),

Don’t forget to import computed and p:

import { signal, computed, component, div, h1, p, button, span, mount } from "@whisq/core";

Save — Vite hot-reloads instantly.

Rule 1: Wrap reactive values in functions

// ❌ WRONG — static, never updates
span(count.value)
// ✅ RIGHT — reactive, updates automatically
span(() => count.value)

Rule 2: Don’t mutate arrays in-place

// ❌ WRONG — won't trigger updates
items.value.push(newItem)
// ✅ RIGHT — creates new array, triggers updates
items.value = [...items.value, newItem]

Rule 3: Events are on* props with functions

// ❌ WRONG — string handler
button({ onclick: "doSomething()" })
// ✅ RIGHT — function handler
button({ onclick: () => doSomething() })