Elements
In Whisq, every HTML element is a function. No JSX, no templates — just function calls with full TypeScript support.
Basic Usage
Section titled “Basic Usage”import { div, h1, p, button, span } from "@whisq/core";
// With props + childrendiv({ class: "card", id: "main" }, h1("Title"), p("Content"),)
// Without props — just childrendiv( h1("Title"), p("Content"),)
// Single text childh1("Hello Whisq")button("Click me")The first argument is either a props object or a child. Whisq detects which automatically.
Available Elements
Section titled “Available Elements”Every standard HTML tag has a function:
Layout: div, span, main, section, article, aside, header, footer, nav
Text: h1–h6, p, strong, em, small, pre, code
Interactive: button, a
Forms: form, input, textarea, select, option, label
Lists: ul, ol, li
Table: table, thead, tbody, tr, th, td
Media: img, video, audio
Misc: br, hr, iframe
Reactive Props
Section titled “Reactive Props”Pass a function for any prop to make it reactive:
import { signal, div, span } from "@whisq/core";
const active = signal(false);const color = signal("blue");const visible = signal(true);
// Reactive class — single getter formdiv({ class: () => active.value ? "card active" : "card" }, span("Content"),)
// Reactive stylediv({ style: () => `color: ${color.value}` }, "Styled text")
// Reactive visibilitydiv({ hidden: () => !visible.value }, "Now you see me")class: array form (since alpha.8)
Section titled “class: array form (since alpha.8)”For composing class names from a mix of static strings, conditional shorthands, and reactive getters, pass an array to class:. Strings are kept; falsy values (false | null | undefined | 0 | "") are filtered out; functions are reactive — each one re-runs when its tracked signals change.
import { signal, div } from "@whisq/core";
const variant = signal<"primary" | "secondary">("primary");const loading = signal(false);const isDisabled = signal(false);
div({ class: [ "btn", // static () => `btn-${variant.value}`, // reactive loading.value && "btn-loading", // static conditional shorthand () => isDisabled.value && "disabled", // reactive conditional ],});If any array element is a function, the whole array is applied reactively; otherwise it’s applied once at mount. This eliminates the cx-vs-rcx decision for most cases — reach for cx / rcx only when you need to compose class strings outside an element prop.
ARIA attributes (since alpha.9)
Section titled “ARIA attributes (since alpha.9)”Typed aria-* props on every element. Like data-*, but with a wider type — ARIA mixes enum-string attrs (aria-live: "polite") with predicate-boolean attrs (aria-expanded, aria-hidden, aria-pressed).
import { button, div, signal } from "@whisq/core";
const menuOpen = signal(false);const status = signal("Ready");
// Staticbutton({ "aria-label": "Remove" }, "×");
// Reactivebutton({ "aria-expanded": () => menuOpen.value }, "Menu");div({ "aria-live": "polite", "aria-atomic": true }, () => status.value);true / false serialise to the strings "true" / "false" (correct per ARIA spec); undefined / null removes the attribute. See /api/elements/#aria-attributes-since-alpha9 for the full table and the pre-alpha.9 boolean-serialisation fix.
Events
Section titled “Events”Events use on* props with function handlers:
button({ onclick: () => count.value++ }, "Click me")input({ oninput: (e) => name.value = e.target.value })form({ onsubmit: (e) => { e.preventDefault(); save(); } }, // form children)All standard DOM events are supported: onclick, oninput, onchange, onsubmit, onkeydown, onmouseenter, onfocus, onblur, etc.
Reactive Children
Section titled “Reactive Children”Pass a function as a child to make it reactive:
const name = signal("World");
div( // Static child — never changes h1("Hello"), // Reactive child — updates when `name` changes p(() => `Welcome, ${name.value}!`),)h() — Low-Level Element Creation
Section titled “h() — Low-Level Element Creation”For dynamic tag names or custom elements:
import { h } from "@whisq/core";
h("custom-element", { class: "foo" }, "content")h("details", { open: true }, h("summary", {}, "Click"), "Hidden content")ref — Direct DOM Access
Section titled “ref — Direct DOM Access”Access the underlying DOM element via the ref prop:
import { signal } from "@whisq/core";
const inputRef = signal<HTMLInputElement | null>(null);
input({ ref: inputRef, type: "text" })
// Later: inputRef.value?.focus()Or with a callback:
input({ ref: (el) => el?.focus(), type: "text",})mount() — Attach to DOM
Section titled “mount() — Attach to DOM”Mount a WhisqNode into a DOM container:
import { div, mount } from "@whisq/core";
const dispose = mount( div("Hello Whisq"), document.getElementById("app")!,);
// Later: dispose() to unmount and clean upNext Steps
Section titled “Next Steps”- Components — Encapsulate elements in reusable components
- Styling — Scoped CSS with
sheet()andstyles()
Docs current to v0.1.0-alpha.9 . All releases →