SVG
SVG inside JSX works without ceremony for the common case (a JSX subtree with <svg> as the root tag). The HTML5 parser recognizes <svg> and switches to “foreign content” mode for its descendants, applying the SVG namespace correctly.
mount(rootEl, () => ( <svg viewBox="0 0 100 100"> <circle cx="50" cy="50" r="40" fill="blue" /> <path d="M 0 50 L 100 50" stroke="red" /> </svg>));The above renders correctly. The diff updates SVG attributes (d, r, fill, transform, …) just like any other attributes.
7.1 The orphan-fragment edge case
Section titled “7.1 The orphan-fragment edge case”The naive parser path breaks for SVG fragments WITHOUT an <svg> wrapper:
const path = '<path d="M 0 0 L 10 10" />';const t = document.createElement('template');t.innerHTML = path;const el = t.content.firstElementChild;// el is HTMLUnknownElement, not SVGPathElement.// el.namespaceURI is "http://www.w3.org/1999/xhtml", not the SVG namespace.// Appending el to a parent <svg> doesn't paint.You hit this when:
- Generating an SVG fragment server-side and inserting it into an existing
<svg>parent. - Building reusable SVG icon helpers that return just a
<g>group. - Composing SVG fragments dynamically.
7.2 The fix: toElement()
Section titled “7.2 The fix: toElement()”toElement(jsx) from kerf detects SVG content and routes through DOMParser with the image/svg+xml MIME, which guarantees correct namespacing for all descendants:
import { toElement } from 'kerfjs';
const path = toElement('<path d="M 0 0 L 10 10" />');// ↑ now an SVGPathElement, namespaced correctly.
const svgRoot = document.querySelector('svg')!;svgRoot.appendChild(path); // paints correctly7.3 What toElement does in detail
Section titled “7.3 What toElement does in detail”- Parses the input through a
<template>.innerHTML(HTML5 parser, which already handles<svg>as foreign content with correct namespacing). - Single-root input (one element with optional surrounding whitespace):
- If the element is
<svg>→ re-parse the input vianew DOMParser().parseFromString(html, 'image/svg+xml')and return the document element. The XML re-parse is strict, so malformed SVG (<svg><unclosed</svg>) throws instead of being silently auto-corrected. - If the element is an orphan SVG-namespace tag (any of
g,path,circle,rect,line,polygon,polyline,ellipse,text,tspan,defs,use,symbol,clipPath,mask,pattern,filter,marker,linearGradient,radialGradient,stop,image,foreignObject) → wrap in<svg xmlns="...">, XML-parse, return the first child. The caller is responsible for parenting it inside an existing<svg>to render. - Otherwise (plain HTML) → return the parsed element directly.
- If the element is
- Multi-root input (multiple elements, or any non-whitespace text alongside an element —
<svg/> label, two icons side by side,text<svg/>) → return the parsedDocumentFragmentas-is. The HTML5 parser already gave any<svg>children the right namespace; the DOM insertion APIs splat the fragment’s children into the parent on insert. Nothing is dropped.
7.4 When you need toElement vs. when mount is enough
Section titled “7.4 When you need toElement vs. when mount is enough”mount()is enough when your SVG has an<svg>root tag in the JSX. The HTML5 parser handles namespacing inside foreign content.toElement()is the escape hatch for direct DOM construction OR for SVG fragments inserted ad-hoc into an existing<svg>.
If you’re not sure which you need, default to mount(). The vast majority of SVG icon and chart use cases work fine without toElement.
7.5 Other namespacing quirks (HTML5 parser oddities)
Section titled “7.5 Other namespacing quirks (HTML5 parser oddities)”A handful of theoretical edge cases that the AST-based approach in some other reactive libs handles but kerf doesn’t, in exchange for a simpler runtime:
- Custom-element
is="..."attributes inside<table>/<select>parents have parser-quirk handling that depends on the surrounding context. Rarely a problem in practice. - Whitespace-only text nodes between sibling elements are sometimes normalized by the parser. Same — rarely matters.
xlink:hrefrequires the alias config the JSX runtime already provides (xlinkHref).
If you hit one of these, file an issue — they’re fixable as a more complete toElement if there’s demand.