Common examples
With Markdoc, it's easy to add functionality you'd commonly associate with documentation sites. The examples below cover loops, syntax highlighting, tabs, and more.
If you're looking for other sample code, check out our collection of example repos, or view the source code for this site.
Loops
Markdoc does not support writing loops directly in documents. If you need to loop through content, do so in a custom Node or Tag transform
function or in a custom React component.
import { Tag } from '@markdoc/markdoc'; export const group = { render: 'Group', attributes: { items: { type: Array } }, transform(node, config) { const attributes = node.transformAttributes(config); const children = node.transformChildren(config); for (const item of attributes.items) { /* Do something with each item */ } return new Tag('Group', attributes, children); } };
{% group items=[1, 2, 3] /%}
Syntax highlighting
You can hook up syntax highlighting for code blocks by creating a custom fence
node. This example shows how to do so with Prism.
// [Source example](https://github.com/markdoc/docs/blob/main/components/Code.js) import 'prismjs'; import 'prismjs/themes/prism.css'; import Prism from 'react-prism'; export function Fence({ children, language }) { return ( <Prism key={language} component="pre" className={`language-${language}`}> {children} </Prism> ); } const fence = { render: 'Fence', attributes: { language: { type: String } } }; const content = Markdoc.transform(ast, { nodes: { fence } }); Markdoc.renderers.react(content, React, { components: { Fence } });
Switch statements
You can create your own switch
/case
semantics with custom Markdoc tags.
import { transformer } from '@markdoc/markdoc'; /** @type {import('@markdoc/markdoc').Config} */ const config = { tags: { switch: { attributes: { primary: { render: false } }, transform(node, config) { const attributes = node.transformAttributes(config); const child = node.children.find( (child) => child.attributes.primary === attributes.primary ); return child ? transformer.node(child, config) : []; } }, case: { attributes: { primary: { render: false } } } } };
which can then be used in your document:
{% switch $item %} {% case 1 %} Case 1 {% /case %} {% case 2 %} Case 2 {% /case %} {% /switch %}
Table of contents
To create a table of contents, first collect all headings from the page content:
// [Source example](https://github.com/markdoc/docs/blob/bae62d06109e3e699778fe901c8015d41b1c7c9f/pages/_app.js#L58-L79) function collectHeadings(node, sections = []) { if (node) { // Match all h1, h2, h3… tags if (node.name.match(/h\d/)) { const title = node.children[0]; if (typeof title === 'string') { sections.push({ ...node.attributes, title }); } } if (node.children) { for (const child of node.children) { collectHeadings(child, sections); } } } return sections; } const content = Markdoc.transform(ast); const headings = collectHeadings(content);
Then, render the headings in a list:
// [Source example](https://github.com/markdoc/docs/blob/main/components/Shell/TableOfContents.js) function TableOfContents({ headings }) { const items = headings.filter((item) => [2, 3].includes(item.level)); return ( <nav> <ul> {items.map((item) => ( <li key={item.title}> <a href={`#${item.id}`}>{item.title}</a> </li> ))} </ul> </nav> ); }
Finally, add IDs to the headings using ID annotations
# My header {% #my-id %}
Tabs
First, create the Markdoc tags
import { Tag } from '@markdoc/markdoc'; const tabs = { render: 'Tabs', attributes: {}, transform(node, config) { const labels = node .transformChildren(config) .filter((child) => child && child.name === 'Tab') .map((tab) => (typeof tab === 'object' ? tab.attributes.label : null)); return new Tag(this.render, { labels }, node.transformChildren(config)); } }; const tab = { render: 'Tab', attributes: { label: { type: String } } }; /** @type {import('@markdoc/markdoc').Config} */ const config = { tags: { tabs, tab } };
Then, create a Tab
and Tabs
React component which map to the tab
and tabs
tag:
// components/Tabs.js import React from 'react'; export const TabContext = React.createContext(); export function Tabs({ labels, children }) { const [ currentTab, setCurrentTab ] = React.useState(labels[0]); return ( <TabContext.Provider value={currentTab}> <ul role="tablist"> {labels.map((label) => ( <li key={label}> <button role="tab" aria-selected={label === currentTab} onClick={() => setCurrentTab(label)} > {label} </button> </li> ))} </ul> {children} </TabContext.Provider> ); };
// components/Tab.js import React from 'react'; import { TabContext } from './Tabs'; export function Tab({ label, children }) { const currentTab = React.useContext(TabContext); if (label !== currentTab) { return null; } return children; }
and use the tags in your document.
{% tabs %} {% tab label="React" %} React content goes here {% /tab %} {% tab label="HTML" %} HTML content goes here {% /tab %} {% /tabs %}