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

Built-in Custom Elements

<md-clock />

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

markdown
<md-clock />

<md-plot>

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

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

Attributes: width, height, color.

<md-counter>

Displays a number with increment/decrement buttons.

markdown
<md-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
<md-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: ['md-clock', 'md-plot', 'md-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: "<md-cl"
Chunk 2: "ock />"

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