8 · JSX SVG via toElement
SVG inside JSX with <svg> as the JSX root works without ceremony — the HTML5 parser switches into “foreign content” mode for descendants, namespaces are correct.
The hairy case is an SVG fragment without an <svg> wrapper. template.innerHTML produces an HTMLUnknownElement, not an SVGPathElement. toElement() is the escape hatch: it routes through DOMParser('image/svg+xml') and returns a properly namespaced node.
What to look at: drag the slider — the circle’s cx updates smoothly because kerf updates only the cx attribute, not the parent <svg> (the diff sees nothing else changed). The grey baseline tick was created via toElement() and prepended into the existing <svg>.
import { signal, mount, delegate, toElement } from 'kerfjs';
const x = signal(50);
const root = document.getElementById('app')!;
mount(root, () => ( <div class="kerf-stack" style="max-width: 24rem;"> <label for="kerf-slider" class="kerf-helper-text">Move the dot</label> <input id="kerf-slider" type="range" min="0" max="100" value={String(x.value)} data-slider style="width: 100%;" /> <p class="kerf-mono" style="display: flex; justify-content: space-between;"> <span>x</span> <strong>{x.value}</strong> </p> <svg id="kerf-example-svg" viewBox="0 0 100 40" width="100%" height="120" style="background: #1f2937; border-radius: 6px;" > <circle cx={String(x.value)} cy="20" r="6" fill="#f59e0b" /> </svg> </div>));
delegate(root, 'input', '[data-slider]', (_, input) => { x.value = Number((input as HTMLInputElement).value);});
// The escape hatch: building an SVG fragment WITHOUT an <svg> wrapper.const tickPath = toElement('<path d="M 0 10 L 100 10" stroke="#9ca3af" stroke-width="0.5" />');document.getElementById('kerf-example-svg')!.prepend(tickPath);