@once-ui-system/core/mdx exports a component map for MDX content. Hand it to next-mdx-remote or @next/mdx and Markdown renders through Once UI: headings get an id and a link to themselves, links go through SmartLink, images through Media, fenced code through CodeBlock, and GFM tables through Table. Common components such as Feedback, Accordion, Card and OgCard are available in the content by name.
The map is typed against the props MDX passes for each element, so an override is checked when you write it, without casts.
Pass mdxComponents as the components prop. The map works in server components, so it can be used with next-mdx-remote/rsc.
next-mdx-remote 6 blocks JavaScript expressions in MDX by default. If your content passes props such as codes={[...]}, set options={{ blockJS: false }}.
@next/mdx reads the map from mdx-components.tsx at the project root (or in src/). Spread the Once UI map first, so the components passed in can override it.
createMdxComponents returns the default map with your entries applied. An HTML tag key (h2, a, hr and so on) replaces what Markdown renders for that element. Any other key registers a component the content can use by name.
- To keep the plain HTML element, map the tag to its own name: createMdxComponents({ hr: "hr" }).
- An entry set to undefined is skipped, so a conditional override cannot remove an element by accident.
- The element components (MdxHeading, MdxParagraph, MdxLink, MdxImage, MdxInlineCode, MdxCodeBlock, MdxBlockQuote, MdxList, MdxListItem, MdxTable, MdxHr) are exported for wrapping. Each takes its element's MDX props plus margins. Setting any margin replaces the element's default margins.
- An override that needs a prop Markdown can never supply does not compile. For example, h3: ({ level }: { level: number }) => ... is rejected.
Tags available by name: Accordion, AccordionGroup, BlockQuote, Button, Card, CodeBlock, Column, Feedback, Grid, Heading, Icon, InlineCode, Line, List, ListItem, Media, OgCard, Row, SmartLink, Table and Text.
Each heading renders with an anchor link next to it. The link shows on hover, on focus, and always on touch screens. It is a real link to the section, so it works without JavaScript and can be reached with the keyboard. Activating it also copies the section's URL and confirms with a toast, so the content must render inside a ToastProvider, as it does in the standard Once UI provider setup. HeadingNav picks up the ids.
For links and images to use next/link and next/image, import LayoutProvider from @once-ui-system/core/next. Without it, they render as a plain <a> and <img>.
Ids are slugged from the text the same way Studio and this site do it: "Get started & deploy" becomes get-started-and-deploy. Two headings with the same text get the same id. To make them unique, add rehype-slug, whose ids take precedence.