Markdown Rendering
AI messages in @threadplane/chat can render full markdown -- headings, code blocks, tables, lists, blockquotes, and inline formatting. The renderMarkdown() utility does the work, and CHAT_MARKDOWN_STYLES handles the styling.
#How It Works
The markdown pipeline has two stages:
- Parse: The
renderMarkdown()function converts markdown text to sanitized HTML using themarkedlibrary (dynamically imported). Ifmarkedis not installed, it falls back to plain text with<br>newline conversion. - Style: The
CHAT_MARKDOWN_STYLESconstant provides CSS rules scoped under the.chat-mdclass, targeting all common markdown elements.
#The renderMarkdown() Function
Signature:
| Parameter | Type | Description |
|---|---|---|
content | string | Raw markdown text to render |
sanitizer | DomSanitizer | Angular's DOM sanitizer for XSS protection |
Returns: SafeHtml -- sanitized HTML that can be bound via [innerHTML].
Behavior:
- When
markedis installed, parses markdown to HTML, sanitizes it through Angular'sSecurityContext.HTML, then marks the result as trusted. - When
markedis not available, escapes HTML entities (&,<,>) and converts newlines to<br>tags.
The marked library is loaded via a dynamic import('marked') at module initialization time. This means it does not block initial bundle loading and resolves before the first render in most cases.
#CHAT_MARKDOWN_STYLES
The CHAT_MARKDOWN_STYLES constant provides CSS rules for all standard markdown elements. It targets the .chat-md class using ::ng-deep for view encapsulation compatibility.
#Styled Elements
The stylesheet covers the following markdown elements:
| Element | Styling |
|---|---|
p | Bottom margin of 0.75em, last child has no bottom margin |
code (inline) | Background var(--ngaf-chat-surface-alt), padding, 4px radius, monospace font |
pre | Background var(--ngaf-chat-surface-alt), 12px 16px padding, horizontal scroll |
pre code | No extra background or padding (inherits from pre) |
ul, ol | 0.5em vertical margin, 1.5em left padding |
li | 0.25em vertical margin |
a | Text color with underline |
strong | Font weight 600 |
blockquote | Left border 3px solid, left padding 12px, muted text color |
h1 | 1.25em font size, weight 600 |
h2 | 1.125em font size, weight 600 |
h3 | 1em font size, weight 600 |
table | Collapsed borders, full width |
th | Alt background, bold, 0.875em |
td | Standard border and padding |
All colors reference --ngaf-chat-* CSS custom properties, so markdown elements automatically respect the active chat theme.
#Using Markdown in Custom Components
Let's render markdown in a custom message template. Apply both the styles and the .chat-md class:
The markdown styles are scoped to .chat-md. Make sure the container element receiving [innerHTML] has this class, otherwise the rendered HTML will appear unstyled.
#Streaming Markdown with chat-streaming-md
<chat-streaming-md> is the component that renders AI message content token-by-token using the node-based rendering pipeline. It resolves each markdown node type against MARKDOWN_VIEW_REGISTRY โ a chat-internal DI token exported from @threadplane/chat.
By default the component provides cacheplaneMarkdownViews (the full 22-node registry) on its own component injector. You can override this at two levels:
- App-wide โ provide a custom registry in your root or feature providers.
- Per-instance โ pass a
ViewRegistryvia the[viewRegistry]input; the component uses that value instead of the DI tree.
#Overriding Markdown Components
#App-wide override
To replace a node-type renderer for every <chat-streaming-md> in your app, provide a custom MARKDOWN_VIEW_REGISTRY in your application config:
overrideViews(base, overrides) replaces every key listed in overrides and preserves all other entries from base. Import it from @threadplane/render (chat does not re-export it).
Use overrideViews when replacing an existing node type. Use withViews when adding a brand-new node type that cacheplaneMarkdownViews does not yet cover โ withViews is additive-only and the base registry wins on conflicts. See the render views API for full signatures.
#Per-instance override
Pass a ViewRegistry directly to a single <chat-streaming-md> via its [viewRegistry] input. The component uses the provided value and ignores the DI tree for that instance:
#Node-Type Reference
cacheplaneMarkdownViews covers every node type emitted by @cacheplane/partial-markdown. Use these keys when calling overrideViews or withViews.
The most common mistake is providing 'code' as an override key โ it does not match anything in the registry. The correct key for fenced code blocks is 'code-block'.
| Key | Description |
|---|---|
'document' | Root node wrapping the entire parsed document |
'paragraph' | Block-level paragraph (<p>) |
'heading' | Heading element (<h1> through <h6>) |
'blockquote' | Block-level quotation (<blockquote>) |
'list' | Ordered or unordered list (<ol> / <ul>) |
'list-item' | Individual list item (<li>) |
'code-block' | Fenced code block (<pre><code>) |
'thematic-break' | Horizontal rule (<hr>) |
'text' | Inline text run |
'emphasis' | Italic emphasis (<em>) |
'strong' | Bold emphasis (<strong>) |
'strikethrough' | Strikethrough text (<del>) |
'inline-code' | Inline code span (<code>) |
'link' | Hyperlink (<a>) |
'autolink' | Auto-detected URL or email link |
'image' | Image (<img>) |
'soft-break' | Soft line break (space or newline within a paragraph) |
'hard-break' | Hard line break (<br>) |
'citation-reference' | In-text citation reference rendered by the chat pipeline |
'table' | Table container (<table>) |
'table-row' | Table row (<tr>) |
'table-cell' | Table header or data cell (<th> / <td>) |
#Theming Markdown Components
All built-in markdown view components consume the same --ngaf-chat-* and --a2ui-* CSS custom properties as the rest of the chat UI. No extra tokens are needed โ changing the active theme automatically re-styles markdown output. See the chat theming guide for the full token reference.
#Without marked
If you skip installing marked, markdown content renders as plain text with line breaks preserved. That's fine for simple chat apps that don't need rich formatting.