Advanced Rich-Text
Beyond basic markdown (bold, italic, links, lists, blockquotes, all rendered automatically through <TinaMarkdown>), nuxt-tina also supports Mermaid diagrams in a rich-text field.
Mermaid diagrams
Add 'mermaid' to the rich-text field's toolbar:
{
type: 'rich-text',
name: 'body',
isBody: true,
overrides: {
toolbar: ['heading', 'link', 'bold', 'italic', 'mermaid', 'codeBlock', /* ... */],
},
}
TinaMarkdown doesn't render Mermaid diagrams by default. mermaid is a large dependency, so it's opt-in:
npm install mermaid
<TinaMarkdown :content="post.body" :components="{ 'tina-mermaid': TinaMermaid }" />
TinaMermaid is auto-imported by the module, so no explicit import is needed. With the components prop wired up, a mermaid code block renders as an SVG diagram through mermaid.render() (dynamically imported only when a diagram is actually mounted, so it never adds to your bundle unless used). Without the prop, mermaid code blocks render as plain code.
Custom templates
A rich-text field can define its own templates (Tina's {{ shortcode }} syntax or MDX-style embeds):
{
type: 'rich-text',
name: 'body',
isBody: true,
templates: [
{
name: 'callout',
label: 'Callout',
match: { start: '{{', end: '}}' },
fields: [
{ name: '_value', type: 'string', label: 'Text', required: true },
],
},
],
}
nuxt-tina doesn't ship a component for any specific custom template: every one renders through <TinaMarkdown> as a generic wrapper: <div data-tina-type="callout">...</div>. Style or target it with that attribute in your own CSS. Unlike Mermaid, there's currently no components-prop hook to swap a named custom template for your own Vue component: extending that coverage is on the roadmap, tracked in the adapter's own source (comark-adapter.ts).
⚠️ Gotcha: templates aren't parsed when format: 'md'
A collection with format: 'md' automatically gets parser: { type: 'markdown' } on its rich-text fields, and that parser mode entirely skips the template-matching logic (templates[].match) inside @tinacms/mdx. Without an override, {{ callout "..." }} just becomes plain text, not a parsed node.
Fix: override the field's parser explicitly:
{
type: 'rich-text',
name: 'body',
isBody: true,
parser: {
type: 'mdx', // force the path that actually preprocesses templates
},
templates: [ /* ... */ ],
}
This applies to both custom shortcodes ({{ }}) and real JSX-style MDX embeds (<Cta ... />).
What a custom template looks like in the AST
If you want to build your own render adapter, a custom template node shows up in the AST as:
{
"type": "mdxJsxFlowElement",
"name": "callout",
"props": { "_value": "This is a callout." },
"children": [{ "type": "text", "text": "" }]
}
comark-adapter.ts already handles this shape: look at the mdxJsxFlowElement/mdxJsxTextElement case in that file for the pattern if you're extending it.