Skip to content

MDX Support

Author Markdown-driven pages that compile to Xote views and can embed Xote components.

MDX support lets Markdown documents compile to Xote views. Use it when you want docs, articles, or rich long-form pages to live as .mdx files while still embedding Xote components.

Status: The MDX runtime is intentionally small. It provides the JSX runtime bridge and component override helpers; your bundler still owns MDX compilation.

Setup

Install the MDX Plugin

Use the MDX Rollup plugin in the Vite app that imports .mdx files.

1npm install --save-dev @mdx-js/rollup

For GitHub Flavored Markdown features such as pipe tables, install remark-gfm too.

1npm install --save-dev remark-gfm

Configure Vite

Point MDX at Xote's automatic JSX runtime.

1// vite.config.js
2import mdx from "@mdx-js/rollup"
3import remarkGfm from "remark-gfm"
4import { defineConfig } from "vite"
5
6export default defineConfig({
7 plugins: [
8 mdx({
9 jsxImportSource: "xote",
10 jsxRuntime: "automatic",
11 remarkPlugins: [remarkGfm],
12 }),
13 ],
14})

The runtime entry points are exported from xote/jsx-runtime and xote/jsx-dev-runtime, which is what MDX uses when jsxImportSource is set to "xote".

Import MDX from ReScript

Bind the compiled MDX module as an Mdx.document, then render it into a View.node.

1// ArticlePage.res
2@module("./article.mdx") external article: Mdx.document = "default"
3
4let app = () => {
5 <main class="article-shell">
6 {Mdx.render(article, ())}
7 </main>
8}
9
10View.mountById(app(), "root")

Component Overrides

Mapping Elements to Components

MDX can receive a components dictionary. Keys match Markdown/HTML element names or custom MDX component names; values are Xote components wrapped with Mdx.component.

1// ArticlePage.res
2type mdxProps = {
3 children?: Mdx.children,
4 href?: string,
5}
6
7module Link = {
8 let make = (props: mdxProps) => {
9 <a href={props.href->Option.getOr("#")} target="_blank" rel="noreferrer">
10 {View.fragment(
11 switch props.children {
12 | Some(children) => Mdx.childrenToNodes(children)
13 | None => []
14 },
15 )}
16 </a>
17 }
18}
19
20let components = Mdx.components([
21 ("a", Mdx.component(Link.make)),
22])
23
24let page = Mdx.render(article, ~components, ())

Rendering Children

MDX children arrive as JavaScript values because the MDX compiler owns the call shape. Use the helpers in Mdx to turn them back into Xote-friendly values.

  • Mdx.childrenToNodes(children) returns an array of View.node.
  • Mdx.childrenToText(children) extracts plain text for cases like syntax highlighting or heading IDs.
1// ArticleHeading.res
2module Heading = {
3 let make = props => {
4 <h2 class="article-heading">
5 {View.fragment(
6 switch props.children {
7 | Some(children) => Mdx.childrenToNodes(children)
8 | None => []
9 },
10 )}
11 </h2>
12 }
13}

Authoring MDX

Markdown and Xote Components

An MDX file can mix Markdown, HTML-like tags, and named components supplied through the components dictionary.

1{/* article.mdx */}
2
3# MDX with Xote
4
5This paragraph renders as Xote nodes.
6
7<Example />
8
9<a href="https://mdxjs.com">MDX link rendered through a Xote component override</a>

Markdown output can be overridden by key:

1// ArticlePage.res
2let components = Mdx.components([
3 ("h1", Mdx.component(ArticleTitle.make)),
4 ("p", Mdx.component(LeadParagraph.make)),
5 ("Example", Mdx.component(Example.make)),
6])

GitHub Flavored Markdown

Enable remark-gfm when your content uses GFM syntax such as tables.

1{/* article.mdx */}
2
3| Feature | Supported |
4| --- | --- |
5| Component overrides | Yes |
6| Markdown tables | Yes, with `remark-gfm` |

Without remark-gfm, Markdown pipe tables are parsed as paragraph text by the base MDX parser.

Runtime Behavior

What the JSX Runtime Handles

Xote's MDX runtime turns MDX's automatic JSX calls into View.node values. It supports:

  • HTML and SVG tags
  • event props such as onClick
  • static, signal, computed, and Prop-wrapped attributes
  • fragments and nested children
  • reserved key handling for keyed reconciliation
  • MDX internal props such as mdxType and originalType

What Stays in the Bundler

MDX parsing, remark plugins, rehype plugins, syntax extensions, and file loading are bundler concerns. Configure those in Vite or whatever build pipeline imports .mdx files.

Working Style

Best Practices

  • Keep MDX content focused on prose and examples; move stateful UI into named Xote components.
  • Use component overrides for links, code blocks, headings, and demo panels so MDX pages match the rest of the app.
  • Type only the props each override needs, then wrap the component with Mdx.component.
  • Add remark-gfm when docs use tables, task lists, autolinks, or strikethrough.

Next Steps