Skip to content

Writing System Prompts for Generative DOM ​

LLMs have no built-in knowledge of Generative DOM. They default to the markdown conventions they were trained on — which means Discord-flavoured GFM, MDX-style curly braces, HTML passthrough, the occasional LaTeX block. Left untold, a model will cheerfully emit constructs that Generative DOM either ignores or strips.

Fix that at the source: tell the model what renderer it's writing for.

The five principles ​

1. Name the target renderer ​

One sentence. Put it at the top of the system prompt.

md
Your output is rendered by GenerativeDom, a streaming markdown renderer. Not plain text, not HTML — markdown that arrives token by token.

This alone cuts a large class of bugs: models stop wrapping output in ```markdown fences, stop HTML-escaping symbols, stop adding "Here is your response:" preambles.

2. Enumerate the supported markdown subset ​

Models are trained on the superset. Spell out what actually renders.

md
Supported markdown:
- Headings (# through ######)
- Bold (**x**), italic (*x*), strikethrough (~~x~~)
- Inline code (`x`) and fenced code blocks with language tags (```ts ... ```)
- Pipe tables with header row and separator row
- Ordered and unordered lists (- or 1.)
- Blockquotes (>)
- Links ([text](https://url)) and images (![alt](https://url))

For the full in/out list, see Generative DOM's Markdown Subset. For the prompt, this seven-line version is enough — the model doesn't need to know why setext headings aren't supported, only that ATX headings are.

3. Enumerate allowed custom elements ​

If you've registered @generative-dom/plugin-custom-elements, the model can emit real web components and they'll render as DOM, not text.

md
You may emit these custom elements; they will render as live web components:
- <ce-clock></ce-clock> — a ticking wall clock (no attributes, no content)
- <ce-plot color="#4a90d9" width="400" height="120">1,2,3,4,5</ce-plot> — bar chart of comma-separated numbers
- <ce-counter start="0" min="0" max="10"></ce-counter> — a number with +/- buttons

These are preserved as web components, not stripped or rendered as text. Every other HTML tag the model emits is either sanitised or shown literally. See Emitting HTML / Custom Elements from LLMs for the full reference, attribute lists, and the interactive + events plugins (ce-button, ce-toggle, ce-input, <progress>, <status>, <milestone>).

4. Token-shape discipline ​

Generative DOM's incremental parser is robust to partial tokens, but the user's reading experience improves dramatically when the model cooperates. Three rules:

  • Prefer short paragraphs. A three-sentence paragraph streams smoothly; a 200-word block renders as a wall of text scrolling past at inference speed.
  • Enumerate list items one at a time. Finish item 1, emit \n- , start item 2. Don't plan all items before writing any of them — the user sees progress as each item lands.
  • Avoid mid-token backticks. The model sometimes writes ` partial\ ` `` at a token boundary. Generative DOM handles this correctly (it waits for the closing backtick before styling), but long unclosed inline-code runs look broken to the user until they close. Close quickly or use fenced blocks for anything longer than ~40 characters.

5. Safety: no raw HTML ​

md
Do not emit HTML outside the custom elements listed above. Tags like <script>, <iframe>, <details>, <summary>, <div>, <span> are stripped or rendered as literal text.

This is defensive prompting, not a security control. Generative DOM's sanitizer runs regardless of what the prompt says — the model can't inject XSS by ignoring instructions. But the prompt prevents the model from trying, which prevents broken-looking output like Here's a &lt;details&gt; block: appearing in the chat.

Sanitization is architectural, not prompt-based

Generative DOM does not use innerHTML anywhere. There is no sanitizer pass to bypass — text always goes through textContent or createTextNode, link URLs are whitelist-checked (http:, https:, mailto: only), and no on* attributes ever reach the DOM. A malicious model trying to emit <script> produces literal text, not a script tag. The principle #5 guidance above is about output quality, not security.

Anti-patterns ​

Things models do by default that you should explicitly forbid.

Raw <script>, <iframe>, <style> ​

Models sometimes paste these when answering security or CSS questions. In Generative DOM they render as literal text (they're not in the custom-element whitelist and don't match any markdown grammar). Tell the model up front.

MDX curly braces ​

md
<!-- Bad: GenerativeDom renders this literally -->
The value is {user.name}.

<!-- Good -->
The value is `user.name`.

Generative DOM is markdown, not MDX. Expressions in {} are text.

<details> / <summary> ​

A common LLM instinct for "long answer". Not supported. Use a heading instead:

md
<!-- Bad -->
<details><summary>Click to expand</summary>
Long explanation here.
</details>

<!-- Good -->
## Long explanation

Explanation here.
md
<!-- Bad: GenerativeDom ignores the [1] and renders [see here][1] as text -->
See the [spec][1] for details.

[1]: https://example.com/spec

<!-- Good -->
See the [spec](https://example.com/spec) for details.

Inline links only. Reference-style links are intentionally unsupported — they require forward lookups that don't stream.

Trailing-whitespace hard breaks ​

Markdown's "two trailing spaces = hard break" rule is visually invisible and fragile. Models drop trailing whitespace when post-processing. Use an explicit blank line or \ at end of line if you need a hard break.

Indented code blocks ​

md
<!-- Bad: four-space indent for code -->
Here is some code:

    const x = 1;

<!-- Good: fenced with language -->
Here is some code:

```ts
const x = 1;

Indented code blocks are intentionally unsupported — they conflict with list indentation in ways that hurt more than they help. Always fence.

## The reusable template block

Paste this at the top of any system prompt where the output is rendered by Generative DOM. Adapt the custom-element list to whichever plugins you've registered.

```md
## Output format

Your response is rendered by GenerativeDom, a streaming markdown renderer.

Use these markdown features:
- Headings (# through ######)
- Bold, italic, strikethrough
- Inline code and fenced code blocks with language tags
- Pipe tables, ordered and unordered lists, blockquotes
- Inline links and images (https:// URLs only)

You may also emit these custom elements:
- <ce-clock></ce-clock> — live ticking clock
- <ce-plot>1,2,3,4</ce-plot> — bar chart of comma-separated numbers
- <ce-counter start="0"></ce-counter> — number with +/- buttons

Do not emit raw HTML outside of that list. No <script>, <iframe>, <details>, <summary>, <div>, <span>. Do not use MDX-style curly braces. Do not use reference-style links. Do not rely on trailing whitespace for line breaks.

Prefer short paragraphs. When listing items, emit them one at a time so the user sees progress as the response streams.

Fifteen lines. Drop it in above your task-specific instructions and the model will stay on the supported subset.

Next ​

See Example System Prompts for six complete, copy-paste-ready prompts covering docs assistants, code tutors, dashboards, data reports, interactive tutorials, and minimal chat.