Skip to main content
Vertz supports rendering raw HTML via the innerHTML prop on any HTML host element. This is the equivalent of React’s dangerouslySetInnerHTML — except the API is a single plain prop, and the compiler blocks the React spelling with a clear error.
The value passed to innerHTML is inserted without escaping. Never pass untrusted input directly. See Safely rendering user content.

When to use it

Use innerHTML only when you already have pre-serialized HTML that you need to render as-is. Common cases:
  • Syntax-highlighted code blocks from a highlighter (Shiki, Prism, highlight.js)
  • Markdown-rendered content produced by a trusted pipeline at build time
  • Sanitized rich-text (e.g. blog post bodies) that passed through a sanitizer
  • Inline SVG icons wrapped in a <span>
For everything else — dynamic text, conditional rendering, attribute binding — use JSX. The framework handles escaping for you.

Basic usage

innerHTML accepts a string, null, or undefined. Nullish values render as empty content (element.innerHTML = ''). It is reactive: when the bound signal changes, the element’s contents update.

What you cannot do

No dangerouslySetInnerHTML

Vertz rejects React’s spelling at compile time:
If you’re migrating from React, change dangerouslySetInnerHTML={{ __html: x }} to innerHTML={x}.

No children together with innerHTML

Setting both would discard one or the other. The compiler raises an error if you do:
Pick one. If you need both a wrapper and raw markup inside, nest them:

No innerHTML on SVG elements

SVG elements reject innerHTML at compile time (E0764) — SVG content serialization uses outerHTML semantics and the DOM innerHTML setter on SVG parses as HTML, not SVG. Wrap SVG markup in a <span> instead:

Void elements

Void HTML elements (<img>, <input>, <br>, <hr>, <meta>, <link>, <area>, <base>, <col>, <embed>, <source>, <track>, <wbr>) cannot contain children — setting innerHTML on them has no effect in the DOM and should be avoided. The compiler does not currently reject this at compile time; treat it as a runtime no-op.

Safely rendering user content

The string passed to innerHTML is inserted verbatim. A <script> tag or an onerror handler in the input will execute. If the content comes from anything a user can influence — form submissions, URL parameters, database rows, API responses — sanitize first. The standard sanitizer is DOMPurify:
Sanitize at the boundary — when the data enters your app — not at the render site. That way every render path for the same data is protected.

The trust boundary

innerHTML has no built-in sanitizer. This is deliberate: sanitization policy is application-specific (what tags, what attributes, what URL schemes), and shipping a default would either be too permissive or too restrictive. Your app owns the trust boundary. The rule of thumb:
  • Sanitize once at the edge (form handler, API response, database read)
  • Mark the result as trusted in your app’s types
  • Render without re-sanitizing via innerHTML
For a value that has already been sanitized and you want the type system to carry that fact, wrap it with the trusted() helper from @vertz/ui:
trusted() is a nominal-type marker. It does not sanitize, transform, or validate. It exists so reviewers can grep for trusted( and audit every place that asserts markup is safe to render.

SSR and hydration

innerHTML works on both the server and the client. On SSR, the value is written into the HTML response verbatim (no escaping, no sanitization). On hydration, the hydrated element’s existing children are preserved — the first reactive update runs after hydration completes so server-rendered content isn’t re-written on initial paint. If you produce the same markup on the server and client, there is no visible flash or mismatch warning.

Full example: syntax-highlighted code

Migrating from React

ReactVertz
<div dangerouslySetInnerHTML={{ __html: x }} /><div innerHTML={x} />
null/undefined → emptySame — nullish → empty
Sanitize with DOMPurifySame — DOMPurify works
Children + dangerouslySetInnerHTML (React warns)Compile error E0761

Summary

  • innerHTML is a plain prop on HTML host elements
  • dangerouslySetInnerHTML is a compile error — use innerHTML
  • Never pass untrusted input; sanitize at the boundary with DOMPurify
  • Wrap sanitized values with trusted() for audit clarity
  • Mutually exclusive with children (E0761); forbidden on SVG elements (E0764)