| name | dom-creation |
| description | How to create DOM elements in rikka — `h()` function, 200+ tag helpers (`div`, `p`, `button`, …), and the `h\`html\`` template literal. Load this when the task is about constructing elements, the `h` API, mounting elements into the document, or HTML/SVG namespace handling. |
DOM Creation
h() and tag helpers create real Element objects — no virtual DOM, no factory, no second call. Append the result directly to the DOM.
Imports
import { h, div, p, button, span, For, Show, When, Switch, Match, css, inlineStyle } from "@takanashi/rikka-dom";
h(tag, attrs?, ...children): Element
import { h } from "@takanashi/rikka-dom";
const el = h("div", { class: "container" }, h("p", "Hello"));
document.body.appendChild(el);
The first argument can be:
- A string tag name (
"div", "my-element", …)
- A custom element constructor (see ../composition/)
The optional second argument is an attrs object. Remaining arguments are children.
Tag helpers
Over 200 pre-defined tag functions share the same signature as h() minus the tag parameter. They return real Element:
import { div, p, button, span, input } from "@takanashi/rikka-dom";
const card = div(
{ class: "card" },
h2({}, "Title"),
p({}, "Body"),
button({ onclick: () => alert("clicked") }, "OK"),
);
document.body.appendChild(card);
The full set:
| Group | Tags |
|---|
| HTML | a, abbr, address, area, article, aside, audio, b, base, bdi, bdo, blockquote, body, br, button, canvas, caption, cite, code, col, colgroup, data, datalist, dd, del, details, dfn, dialog, div, dl, dt, em, embed, fieldset, figcaption, figure, font, footer, form, h1–h6, head, header, hgroup, hr, html, i, iframe, img, input, ins, kbd, label, legend, li, link, main, map, mark, menu, meta, meter, nav, noscript, object, ol, optgroup, option, output, p, picture, pre, progress, q, rp, rt, ruby, s, samp, script, search, section, select, slot, small, source, span, strong, style, sub, summary, sup, table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track, u, ul, var_, video, wbr |
| SVG (no prefix — auto-resolves namespace) | svg, animate, animateMotion, animateTransform, circle, clipPath, defs, desc, ellipse, feBlend, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, feDisplacementMap, feDistantLight, feDropShadow, feFlood, feFuncA, feFuncB, feFuncG, feFuncR, feGaussianBlur, feImage, feMerge, feMergeNode, feMorphology, feOffset, fePointLight, feSpecularLighting, feSpotLight, feTile, feTurbulence, filter, foreignObject, g, image, line, linearGradient, marker, mask, metadata, mpath, path, pattern, polygon, polyline, radialGradient, rect, set, stop, switch_, symbol, text, textPath, tspan, use, view |
| MathML (no prefix — auto-resolves namespace) | math, annotation, annotationXml, maction, menclose, merror, mfenced, mfrac, mglyph, mi, mlabeledtr, maligngroup, malignmark, mmultiscripts, mn, mo, mpadded, mphantom, mprescripts, mroot, mrow, ms, mspace, msqrt, mstyle, msub, msubsup, msup, mtable, mtd, mtext, mtr, munder, munderover, none, semantics |
| SVG-prefixed (HTML/SVG name collisions) | svga, svgscript, svgstyle, svgtitle, svgtext, svgspan, svgtextPath |
var_, switch_ use a trailing underscore because var and switch are JavaScript reserved words. Use import { var_ as v } from "@takanashi/rikka-dom" if you need the un-suffixed name.
See ../svg/ for namespace handling.
h\html`: Element[]` (template literal)
HTML template literal with signal interpolation. Returns an Element[], not a single element. Use [0] to get a single element.
import { h } from "@takanashi/rikka-dom";
import { signal } from "@takanashi/rikka-signal";
const name = signal("World");
const elements = h`<span>Hello ${name}!</span>`;
document.body.appendChild(elements[0]);
Signal interpolation modes
| Syntax | Mode | Behavior |
|---|
${signal} | Fine-grained | Creates an effect; only the text/attr node updates. Preserves focus, cursor, scroll position. |
${signal.get()} | Lost reactivity | Resolved immediately to a static value. |
computed(() => h\...`)` | Coarse-grained | Entire template rebuilds when a dependency changes. |
const count = signal(0);
h`<span>Count: ${count}</span>`;
h`<span>Count: ${count.get()}</span>`;
const tmpl = computed(() => h`<span>Count: ${count.get()}</span>`);
Attribute interpolation works the same way:
const color = signal("red");
h`<div style="color: ${color}">Text</div>`;
Getting an HTMLTemplateElement
Use h\`[0]to get a realHTMLTemplateElementfordefineElement's template` option:
const tmpl = h`<template><div>{{name}}</div></template>`[0];
tmpl instanceof HTMLTemplateElement;
tmpl.content;
Mounting the app
h() returns a real Element, so use standard DOM APIs:
import { div, p, button } from "@takanashi/rikka-dom";
import { signal } from "@takanashi/rikka-signal";
const count = signal(0);
const app = div(
{},
p({}, "Count: ", count),
button({ onclick: () => count.set(count.get() + 1) }, "+"),
);
document.body.appendChild(app);
rikka-dom also exports applyChild for cases where you need to apply a child without appendChild (e.g. tests):
import { applyChild } from "@takanashi/rikka-dom";
const container = document.getElementById("app");
if (container) applyChild(container, app);
Children
The Child type accepts: null | string | number | boolean | Element | DocumentFragment | ReactiveRange | Signal.State<Child> | Signal.Computed<Child> | Child[] | Signal.State<Child[]> | Signal.Computed<Child[]> | (() => Child).
Booleans (true/false) render nothing — useful for && conditional patterns like div({}, show && p({}, "visible")).
The interesting case is the function — it's auto-wrapped as computed so any .get() inside is tracked. See ../signal-binding/#function-children for details.
const count = signal(0);
div({}, () => `count=${count.get()}`);
const visible = signal(true);
div({}, () => (visible.get() ? span({}, "on") : null));
Namespace handling
rikka-dom auto-selects the namespace based on tag name:
- HTML tags →
http://www.w3.org/1999/xhtml
- SVG-only tags (
circle, path, g, …) → http://www.w3.org/2000/svg
- MathML-only tags →
http://www.w3.org/1998/Math/MathML
- Name-collision tags (
a, script, style, title, text, span, textPath) → use the svg prefix in SVG context: svga, svgscript, svgstyle, svgtitle, svgtext, svgspan, svgtextPath
See ../svg/.
Pitfalls
1. h\`` returns an array
document.body.appendChild(h`<div>Hello</div>`);
document.body.appendChild(h`<div>Hello</div>`[0]);
2. NumberAttr default is NaN (not 0)
Number(undefined) returns NaN. If you want a default, declare it:
defineElement("my-el", {
attributes: { count: { ...NumberAttr, default: 0 } },
});
Or use a custom toProp:
attributes: {
count: { toProp: (v) => (v !== undefined ? Number(v) : 0) },
}
3. Never use innerHTML
It bypasses the signal system, has XSS risk, and prevents binding:
el.innerHTML = `<div>${title}</div>`;
el.replaceChildren(div({}, title));
4. Plain function as a value (not a child) is NOT reactive
Function children are auto-wrapped as computed, but function values (e.g. as an attribute, or in inlineStyle arg) are not:
div({}, () => `count=${count.get()}`);
div({ style: { color: () => count.get() % 2 ? "red" : "blue" } });
div({ style: { color: computed(() => count.get() % 2 ? "red" : "blue") } });
See ../signal-binding/#function-values-vs-computed.
See also