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/editor

v0.2.0

Published

Rich, source and preview Markdown editing for React. Markdown in, Markdown out.

Readme

@react-markdown-kit/editor

A React Markdown editor with rich, source and preview modes. Markdown in, Markdown out. Built on Lexical, but Lexical never appears in the public types. Open a document and close it without typing, and the file is unchanged.

npm install @react-markdown-kit/editor @react-markdown-kit/renderer
'use client'

import { useState } from 'react'
import { MarkdownEditor } from '@react-markdown-kit/editor'

export function Notes() {
  const [value, setValue] = useState('# Notes\n\nStart writing.')
  return <MarkdownEditor value={value} onChange={setValue} />
}

Your application keeps storing Markdown strings. Nothing asks you to persist editor JSON or a proprietary format.

Links

The round trip is the point

A Markdown editor that parses into an editing model and serializes back out can damage the document on every save, and the damage compounds.

This editor converts between a real CommonMark syntax tree and the editing model, then writes unchanged blocks back from their original source bytes. Opening a document and closing it changes nothing.

The test corpus comes from docs/AUDIT.md, an audit of a prior editor with seven reproducible corruption bugs, expanded to 22 cases. All 22 round-trip byte for byte through the editor bridge, each asserted twice: once with GitHub Flavored Markdown and once with plain CommonMark (packages/editor/tests/roundtrip.test.ts). A second suite, tests/roundtrip.test.ts, checks the serializer path with no source bytes to fall back on: 22 of 22 keep their meaning, 22 of 22 are idempotent, 14 of 22 are byte-identical, and the 14 may not shrink.

> a            blockquote with a blank line     unchanged
> 
> b

C:\path\to     Windows paths                    unchanged
~~~js          tilde fences                     unchanged
[t][ref]       reference links                  unchanged
<div>…</div>   HTML blocks                      unchanged
3. three       explicit list numbering          unchanged

Unsupported syntax is preserved, not flattened. Anything the editor has no rich representation for becomes an opaque block that keeps its exact source slice. Editing the paragraph next to it cannot touch it, and moving it around keeps it intact.

Three modes

<MarkdownEditor mode={mode} onModeChange={setMode} />

rich for WYSIWYG, source for raw Markdown, preview for read-only output.

Preview delegates to @react-markdown-kit/renderer rather than shipping a second renderer, so what you preview is what your application renders.

Typing Markdown in rich mode

Markdown typed on the rich surface turns into the block or format it names.

| Type | Result | |---|---| | # to ###### | Heading | | - , * , + | Bullet list | | 1. | Ordered list, starting at the typed number | | [ ] , [x] | Task item, also at the start of a bullet item | | > | Blockquote | | ```ts then space or Enter | Code block with that language | | ---, ***, ___ then Enter | Thematic break | | \| a \| b \| then Enter | Table with that header; later row lines join it, and \| :-- \| --: \| sets alignment | | **bold**, *em*, `code`, ~~strike~~ | Inline formats, on the closing character | | [text](url), ![alt](src) | Link or image, on the closing ) |

Undo after a shortcut gives back the typed text. Only a typed character triggers one: loading, pasting, deleting or undoing never does. Nothing converts inside a code block, and tables, task items and ~~ stay as typed in a CommonMark document. In a list, Enter starts the next item, Enter on an empty item leaves the list, and Backspace at the start of an item removes its checkbox and then the bullet. In a blockquote, Enter on an empty last line leaves the quote.

Controlled or uncontrolled

<MarkdownEditor value={value} onChange={setValue} />
<MarkdownEditor defaultValue="# Hello" onChange={handleChange} />
<MarkdownEditor />

An echoed value does not reset selection or undo history. To replace the document deliberately, change documentKey:

<MarkdownEditor documentKey={note.id} value={note.markdown} onChange={setMarkdown} />

Build your own interface

The default editor ships a toolbar so it is useful immediately. When you need your own, keep the engine and replace the chrome:

const editor = useMarkdownEditor({ value, onChange, preset })

<MarkdownEditorProvider editor={editor}>
  <MyToolbar />
  <MarkdownEditorContent />
  <MyStatusBar />
</MarkdownEditorProvider>

The instance exposes getMarkdown(), getDocument(), focus(), undo(), redo(), setMode() and commands. No Lexical type appears in the public types, so your components never import the engine. getNativeEditor() is available as an escape hatch and is explicitly implementation-coupled with weaker stability guarantees.

Images

The application owns storage. The editor owns the interaction.

<MarkdownEditor
  onUploadImage={async (file, context) => {
    const asset = await upload(file, context.signal)
    return { src: asset.url, alt: asset.alt ?? '' }
  }}
/>

A blob: URL is never written into the document.

Shared dialect

Define what Markdown means once, and use it in the renderer, the editor and the variables plugin:

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

export const appMarkdown = defineMarkdownPreset({ extensions: [gfm()] })
<MarkdownEditor preset={appMarkdown} value={value} onChange={setValue} />

Styling

No styling dependency: no Tailwind, no design system, no icon package. Icons are inline SVG.

The editor is fully functional with no stylesheet loaded. packages/editor/tests/styling.test.tsx edits, switches mode and serializes with no CSS in the document. The optional theme is opt-in and scoped:

import '@react-markdown-kit/editor/styles.css'
.rmk-editor { --rmk-border: #cfe0dd; --rmk-radius: 8px; }

Or pass your own classes per part, which replace rather than merge:

<MarkdownEditor
  classNames={{
    root: 'rounded-lg border border-slate-200',
    toolbar: 'flex gap-1 border-b p-2',
    content: 'min-h-64 p-4',
  }}
/>

Full contract in docs/STYLING.md.

Headless, without React

The same bridge the React editor uses, for a server or a script:

import { createMarkdownBridge, serializeDocument } from '@react-markdown-kit/editor'

Exports

| Export | Purpose | | --- | --- | | MarkdownEditor (also default) | The batteries-included editor | | useMarkdownEditor | Headless instance | | MarkdownEditorProvider | Context for composable layouts | | MarkdownEditorContent | The editable surface | | useMarkdownEditorContext | Read the instance from context | | useOptionalMarkdownEditorContext | The same, null outside a provider, for chrome that also takes an editor prop | | createMarkdownBridge, serializeDocument | Markdown in and out, no React |

Extensions that bring their own block

The root entry never names Lexical. An extension that needs its own editor node imports the one entry that does:

import { lexicalAdapter, type EditorBlockAdapter } from '@react-markdown-kit/editor/lexical'

export function myBlocks(): MarkdownExtension {
  return {
    name: 'my-blocks',
    capabilities: {
      syntax: { nodeTypes: ['myBlock'], transform, toMarkdownExtensions },
      editor: lexicalAdapter({
        nodes: [MyBlockNode],
        blocks: [adapter],          // mdast `myBlock` <-> MyBlockNode, and back
        plugins: [registerInsert],  // (editor) => unregister
        commands: [{ id: 'myBlock', label: 'Insert block', icon, run }],
      }),
    },
  }
}

A block adapter's $export returns the original source while the block is untouched, so the writer hands back those exact bytes; once edited it returns null and the block serializes through the extension's toMarkdownExtensions. An inline adapter (inlines) does the same for an inline node such as a chip; its $export returns the mdast plus its Markdown spelling, which is the node's identity for the writer. A decorator component reaches the engine with useLexicalEditor(). @react-markdown-kit/mermaid/editor (a block), @react-markdown-kit/slides/editor (three blocks and an Enter shortcut) and @react-markdown-kit/variables/editor (an inline) are the shipped examples.

Plugins

Variables, Mermaid flowcharts and slides are separate packages, used only through extensions:

import { variableChips } from '@react-markdown-kit/variables/editor'
import { mermaid } from '@react-markdown-kit/mermaid/editor'
import { slides } from '@react-markdown-kit/slides/editor'

<MarkdownEditor
  value={source}
  onChange={setSource}
  extensions={[variableChips({ previewData: sample }), mermaid(), slides()]}
/>

Every {{placeholder}} becomes a chip with a sample-data preview (never written back to the source), a ```mermaid flowchart opens on a drawing canvas, and a deck gets numbered slide breaks, ??? / -- dividers, directive chips, four toolbar buttons and a Preview that is the interactive deck. See each package's README: variables, mermaid, slides.

License

MIT