npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vaneui/md

v1.1.0

Published

Markdown component for VaneUI - A React component that renders markdown using @vaneui/ui components

Downloads

1,805

Readme

@vaneui/md

A React Markdown renderer that parses Markdown with Markdoc and renders every node as a @vaneui/ui component (# heading becomes Title, > quote becomes Card, `code` becomes Code, and so on).

@vaneui/ui is a peer dependency; this package does not render standalone.

Installation

npm install @vaneui/md @vaneui/ui

yaml is an optional peer dependency, needed only if you use the frontmatter parser or vaneui fences (see below).

Basic usage

import { Md } from "@vaneui/md";

export default function Page() {
  return (
    <Md
      content={`
# Hello world

This is a **Markdown** document rendered with VaneUI components.

- Item one
- Item two

> A blockquote.
`}
    />
  );
}

The host app must wrap this content in @vaneui/ui's ThemeProvider and import one @vaneui/ui stylesheet (import "@vaneui/ui/css"), otherwise the output renders unstyled.

To get the document prose rhythm, import the styles once in your app (alongside your @vaneui/ui CSS):

import "@vaneui/md/styles";

Props

| Prop | Type | Description | |---|---|---| | content | string | The Markdown source to render. Required. | | frontmatter | Record<string, unknown> | Pre-parsed frontmatter. Wins over parseFrontmatter. Exposed in Markdown as $markdoc.frontmatter. | | parseFrontmatter | (raw: string) => Record<string, unknown> | Parser for the raw YAML between --- markers. Also parses vaneui fences. Throwing routes to an inline error. | | components | Record<string, React.ComponentType> | String to component map used to resolve names in vaneui fences. Use defaultRegistry from @vaneui/md/registry, or supply your own. | | rendererTheme | MdRendererTheme | Per-renderer visual defaults (one slot per Md* renderer). | | config | MdConfig | Override Markdoc nodes, components, tags, functions, or variables. |

Frontmatter

<Md> does not parse YAML by default. Pass already-parsed data via frontmatter, or a parser via parseFrontmatter. Both expose data at $markdoc.frontmatter for interpolation. The raw string is always available at $markdoc.frontmatterRaw.

import { Md } from "@vaneui/md";
import { parseYamlFrontmatter } from "@vaneui/md/yaml";

// Auto-parse YAML
<Md content={md} parseFrontmatter={parseYamlFrontmatter} />

// Bring your own parser
<Md content={md} parseFrontmatter={(raw) => myParser(raw)} />

// Pre-parsed (skips parseFrontmatter)
<Md content={md} frontmatter={{ title: "Hello" }} />

Resolution order: frontmatter prop, then parseFrontmatter(rawFrontmatter), then undefined. Malformed YAML renders an inline error above the body and never throws.

The vaneui fence

A fenced code block tagged vaneui is parsed as YAML and rendered as a real VaneUI component tree at that spot in the document. No JSX and no build step are involved. Two things must be supplied to <Md>: parseFrontmatter (the YAML parser) and components (the name-to-component registry).

import { Md } from "@vaneui/md";
import { parseYamlFrontmatter } from "@vaneui/md/yaml";
import { defaultRegistry } from "@vaneui/md/registry";

<Md
  content={source}
  parseFrontmatter={parseYamlFrontmatter}
  components={defaultRegistry}
/>

Shorthand form

The component name and boolean flags go in the YAML key. The value is read by shape.

```vaneui
Card primary filled:
  - Title xl: Welcome
  - Text: Everything you need to ship.
```

| Value shape | Becomes | |---|---| | empty | leaf with no children | | string | children | | list | children (expanded recursively) | | map | props (href, src, explicit children, and so on) |

For non-boolean props, use the map form:

```vaneui
Button primary filled:
  href: /get-started
  text: Get started
```

Component names must start with an uppercase letter. That is how a component spec is told apart from a plain prop map, so lowercase keys are left as data.

Verbose form

Both forms compose in the same tree. Use the verbose form when you want the explicit component key.

```vaneui
component: Card
primary: true
children:
  - component: Title
    xl: true
    text: Welcome
```

Registry safety

defaultRegistry (@vaneui/md/registry) is a curated allowlist of presentational components. A name not in the registry renders nothing rather than throwing, and stateful or callback components (modals, popups, inputs, and similar) are excluded. Supply your own map to widen or narrow the set. Recursion is capped to keep deeply nested specs bounded.

Per-renderer visual defaults: rendererTheme

Each Md* renderer has a slot in MdRendererTheme. A slot holds boolean props that the renderer spreads onto its underlying VaneUI element, so two renderers that share a primitive (for example MdBlockquote and MdFence both render Card) can be styled independently.

<Md
  content={md}
  rendererTheme={{
    mdBlockquote: { primary: true },   // appearance only, keeps the left bar
    mdCode:       { success: true },
    mdFence:      { tertiary: true },  // code-block wrapper, separate from blockquote
  }}
/>

Slot keys mirror the renderer file names (MdBlockquote -> mdBlockquote, MdFence -> mdFence). You can also share a theme across instances with RendererThemeContext.Provider. The rendererTheme prop wins over an outer provider.

The merge is group-aware for VaneUI's mutually-exclusive categories (size, appearance, variant, shape, border, and so on): setting any key in a group drops the package defaults for that group, so your intent wins over the built-in defaults.

Customizing renderers: config

Override any part of the default configuration. The merge is shallow per key.

<Md
  content={md}
  config={{
    components: { MdHeading: CustomHeading },  // swap a renderer
    nodes: { fence: customFenceNode },          // change a node's Markdoc config
    tags: { callout: calloutTag },              // add a Markdoc {% callout %} tag
  }}
/>

config accepts nodes, components, tags, functions, and variables, all passed through to Markdoc.

Syntax highlighting

Code fences render as a plain <pre> by default. Pass a highlight hook to render highlighted code. Two engines ship as optional subpaths; install the one you use.

import { Md } from "@vaneui/md";
import { prismHighlighter } from "@vaneui/md/prism";

<Md content={md} highlight={prismHighlighter()} />

Shiki uses a synchronous core, so you import the languages and themes you need and pass them in:

import { shikiHighlighter } from "@vaneui/md/shiki";
import tsx from "shiki/langs/tsx.mjs";
import githubDark from "shiki/themes/github-dark.mjs";

<Md content={md} highlight={shikiHighlighter({ langs: [tsx], themes: [githubDark] })} />

The hook signature is (code, language, meta?) => ReactNode, so any function of that shape works as a custom engine.

Presets and createMd()

A preset bundles renderer configuration: nodes, tags, functions, variables, renderer swaps (components), vaneui-fence allowlist entries (registry), rendererTheme, highlight, transform, sanitize, and co-shipped styles. Compose presets with createMd().use(...):

import { createMd } from "@vaneui/md";
import { shikiPreset } from "@vaneui/md/shiki";

const Doc = createMd().use(shikiPreset({ langs, themes }));
<Doc content={md} />

Or fold presets on a single render with the presets prop:

<Md content={md} presets={[shikiPreset({ langs, themes })]} />

Presets merge left to right. Two presets that define the same tags, components, functions, or registry key log a development warning and the last one wins. Explicit <Md> props win over preset-supplied values.

Safe rendering of untrusted content

The vaneui fence resolves component names through the registry allowlist, and props are always sanitized: dangerouslySetInnerHTML, event handlers, and ref are stripped, and javascript:, vbscript:, and non-image data: URLs are dropped from href/src. To tighten further for user- or AI-authored content, pass a sanitize policy or use untrustedPreset:

import { Md, untrustedPreset } from "@vaneui/md";

<Md content={userMarkdown} presets={[untrustedPreset]} parseFrontmatter={parseYamlFrontmatter} components={registry} />

A SanitizePolicy can set allowedProtocols, blockedProps, and allowComponents.

Post-transform hook

The transform prop runs on Markdoc's renderable tree after transform and before render, so it can rewrite nodes. Built-in helpers: headingAnchors(options?) and rewriteLinks(fn).

import { Md, headingAnchors } from "@vaneui/md";

<Md content={md} transform={headingAnchors()} />

headingAnchors accepts { slug, link, name }. Pass slug to match your own anchor convention (for example a docs site whose table of contents computes ids a specific way), link: true to wrap each heading in a plain <a href="#id">, and name when you render headings under a non-default component name.

<Md content={md} transform={headingAnchors({ slug: myToId, link: true })} />

Streaming

MdStream renders growing markdown (for example token-by-token LLM output) by splitting it into fence-aware blocks and memoizing each, so only the changed trailing block re-parses:

import { MdStream } from "@vaneui/md";

<MdStream content={streamingText} highlight={prismHighlighter()} />

Exports

  • @vaneui/mdMd, MdStream, createMd, all Md* renderers, mergePresets, untrustedPreset, useMdStream, renderSpec, expandShorthand, collapseShorthand, visit, headingAnchors, rewriteLinks, defaultNodesConfig, defaultComponents, defaultRendererTheme, mergeRendererTheme, strictSanitizePolicy, the contexts (RegistryContext, ParserContext, HighlightContext, RendererThemeContext, SanitizeContext), and the types (MdPreset, MdTransform, HighlightFn, SanitizePolicy, and more).
  • @vaneui/md/shikishikiHighlighter, shikiPreset. shiki is an optional peer dependency.
  • @vaneui/md/prismprismHighlighter, prismPreset. prism-react-renderer is an optional peer dependency.
  • @vaneui/md/yamlparseYamlFrontmatter, a thin wrapper over yaml.parse. yaml is an optional peer dependency.
  • @vaneui/md/registrydefaultRegistry, the safe component allowlist. Imported only when used, so consumers who do not render vaneui fences pay no bundle cost.
  • @vaneui/md/styles — the .vaneui-md prose-rhythm layer. It adds spacing rules built on VaneUI tokens; it does not re-bundle @vaneui/ui CSS. Import it alongside your VaneUI CSS.

Development

npm install
npm run type-check   # tsc --noEmit
npm test             # type-check + jest
npm run build        # clean + rollup (CJS + ESM) + postcss
npm run playground   # install + run the playground dev server

License

ISC