Skip to content

Custom Elements ​

Generative DOM uses Web Components (Custom Elements) as its rendering target for custom syntax plugins. This guide explains how Custom Elements integrate with Generative DOM.

Why Web Components? ​

  • Standards-based. Custom Elements are a browser-native API, not a framework abstraction.
  • No framework lock-in. Components work in any context: vanilla JS, React, Vue, Svelte, or Angular.
  • Encapsulated styling. Shadow DOM allows plugins to style their output without leaking into the host page.
  • Lifecycle hooks. connectedCallback and disconnectedCallback let components set up and tear down resources cleanly.

Using the Custom Elements Plugin ​

The custom-elements plugin parses HTML-like tags from the markdown stream and renders them as actual Custom Elements:

ts
import { GenerativeDom } from '@generative-dom/core';
import { markdownBase, customElements } from '@generative-dom/plugins';

const md = new GenerativeDom({
  container: document.getElementById('output'),
  plugins: [markdownBase(), customElements()],
});

md.push('<ce-clock />\n\n<ce-plot color="blue">1,2,4,7,3</ce-plot>');
md.flush();

Built-in Custom Elements ​

<ce-clock /> ​

Displays a live ticking clock. Self-contained, no attributes needed. Uses setInterval internally, cleaned up on disconnect.

markdown
<ce-clock />

<ce-plot> ​

Takes comma-separated numbers and renders an inline SVG bar chart.

markdown
<ce-plot color="blue" width="200" height="100">1,2,4,7,3</ce-plot>

Attributes: width, height, color.

<ce-counter> ​

Displays a number with increment/decrement buttons.

markdown
<ce-counter start="5" min="0" max="10" />

Attributes: start (initial value, default 0), min, max.

Security ​

Only whitelisted custom element tags are processed. Unknown tags are rendered as plain text. This prevents arbitrary HTML injection:

markdown
<evil-script />       --> rendered as plain text
<ce-clock />          --> rendered as a Custom Element

Attributes are filtered through a safe whitelist. Event handler attributes (onclick, onerror, etc.) are always stripped. The style attribute is blocked if it contains url() or expression().

Configuring Allowed Tags ​

The custom-elements plugin accepts a whitelist of allowed tag names:

ts
customElements({
  allowedTags: ['ce-clock', 'ce-plot', 'ce-counter', 'my-widget'],
})

Only tags in this list will be parsed and rendered. All others are treated as plain text.

Stream Handling ​

Custom element tags are parsed correctly even when the stream splits mid-tag:

Chunk 1: "<ce-cl"
Chunk 2: "ock />"

The parser waits for the complete tag before rendering. Partial tags remain in the buffer until enough content arrives.