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

@react-markdown-kit/renderer

v0.1.0

Published

Render Markdown as React. Safe by default, unstyled by default, extensible.

Readme

@react-markdown-kit/renderer

Render Markdown as React.

npm install @react-markdown-kit/renderer
import Markdown from '@react-markdown-kit/renderer'

export function Article({ content }: { content: string }) {
  return <Markdown>{content}</Markdown>
}

That is the whole first-use experience. No provider, no stylesheet, no account, no design system, no configuration.

What you get by default

  • CommonMark, parsed with micromark rather than regular expressions.
  • Safe rendering. Raw HTML is not executed and unsafe URL schemes are blocked. See Security.
  • Unstyled semantic HTML. No classes, no inline styles, no CSS to fight.
  • Server-ready. No "use client" on this entry, so it works in server components, SSR and static rendering.

Custom components

<Markdown components={{ a: AppLink, img: AppImage, code: CodeBlock }}>
  {content}
</Markdown>

GitHub Flavored Markdown

Tables, task lists, strikethrough, autolinks and footnotes. Two equivalent routes:

import { GfmMarkdown } from '@react-markdown-kit/renderer/gfm'

<GfmMarkdown>{content}</GfmMarkdown>
import Markdown, { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'

const preset = defineMarkdownPreset({ extensions: [gfm()] })

<Markdown preset={preset}>{content}</Markdown>

The remark-gfm plugin also works, and the test suite asserts the two produce the same output.

Presets: define your dialect once

A preset is your application's answer to "what does Markdown mean here?" Define it once and reuse it in the renderer, the editor and the template engine.

// markdown.ts
import { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'

export const appMarkdown = defineMarkdownPreset({
  extensions: [gfm()],
  components: { a: AppLink, img: AppImage },
})
<Markdown preset={appMarkdown}>{content}</Markdown>

Compose presets rather than copying them:

export const docsMarkdown = defineMarkdownPreset({
  extends: [appMarkdown],
  components: { a: DocsLink },
})

Merge rules are deterministic and documented: extensions merge by name with the later one replacing the earlier in place, components and class names shallow merge with the later winning, and a policy option overrides the preset only when you pass it explicitly.

Compile once, render many times

import { compileMarkdown } from '@react-markdown-kit/renderer'

const doc = compileMarkdown(source, { preset: appMarkdown })

<Markdown document={doc} />

compileMarkdown is deterministic and does no I/O. The document it returns is plain JSON: no React elements, no class instances, no closures. You can cache it, send it over the wire, or hand it to the template engine.

children and document are mutually exclusive in the types, so there is no "which one wins" rule to remember.

A precompiled document still runs every transform and the full security policy. Passing a document is not a way around sanitization.

Styling

The kit ships no styling dependency: no Tailwind, no design system, no CSS-in-JS runtime. Four approaches all work against the same renderer.

Bring your own CSS. The default output is clean semantic HTML, so style it however you like.

Opt into the shipped typography and retint it with custom properties:

import '@react-markdown-kit/renderer/styles.css'

<div className="rmk-document">
  <Markdown>{content}</Markdown>
</div>
.rmk-document { --rmk-link: rebeccapurple; --rmk-measure: 60ch; }

Utility classes, passed per part. They replace rather than merge, so there is nothing to !important away:

<Markdown classNames={{ heading: 'text-2xl font-bold', code: 'rounded bg-slate-100 px-1' }}>
  {content}
</Markdown>

Your own components, from any library:

<Markdown components={{ a: AppLink, table: AppTable }}>{content}</Markdown>

Full contract in docs/STYLING.md.

Security

Raw HTML in the source is not executed, and href/src-style attributes are restricted to safe schemes. The URL algorithm is the same one react-markdown uses, because it is well tested against protocol-obfuscation corpora.

<Markdown>{'[x](javascript:alert(1))'}</Markdown>
// href is emptied, nothing executes

To allow raw HTML, opt in explicitly and sanitize:

import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'

<Markdown skipHtml={false} rehypePlugins={[rehypeRaw, rehypeSanitize]}>
  {content}
</Markdown>

Plugins and components you pass are trusted application code. They are not sandboxed, and the documentation does not claim otherwise.

remark and rehype

The existing ecosystem keeps working:

<Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
  {content}
</Markdown>

Policy runs after every plugin, so a plugin cannot inject markup that skips sanitization.

Coming from react-markdown

The prop surface is intentionally familiar: children, components, remarkPlugins, rehypePlugins, remarkRehypeOptions, allowedElements, disallowedElements, allowElement, skipHtml, unwrapDisallowed and urlTransform.

See docs/COMPATIBILITY.md for the evidence-backed matrix against a pinned [email protected], where every feature is classified compatible, compatible with documented change, not supported yet or intentionally different.

We do not claim "drop-in replacement". Read the matrix and decide.

The rest of the kit

All optional. The renderer never requires them, and carries none of their code.

API

| Export | Purpose | | --- | --- | | Markdown (also default) | The component | | compileMarkdown(source, options?) | Markdown to MarkdownDocument | | documentToMarkdown(document, options?) | Back to a Markdown string | | defineMarkdownPreset(options?) | Define a dialect once | | gfm(options?) | GitHub Flavored Markdown extension | | defaultUrlTransform(url) | The default URL policy, for reuse | | isMarkdownDocument(value) | Runtime guard |

License

MIT