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:
- <md-clock></md-clock> — a ticking wall clock (no attributes, no content)
- <md-plot color="#4a90d9" width="400" height="120">1,2,3,4,5</md-plot> — bar chart of comma-separated numbers
- <md-counter start="0" min="0" max="10"></md-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 (md-button, md-toggle, md-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:
- <md-clock></md-clock> — live ticking clock
- <md-plot>1,2,3,4</md-plot> — bar chart of comma-separated numbers
- <md-counter start="0"></md-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.