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

@gotocva/react-markdown

v1.0.0

Published

React + Vite library: render Markdown into styled HTML and edit it with a Notion-style block editor (slash menu, bubble menu, syntax-highlighted code blocks, resizable tables with auto S.No, GFM round-trip).

Readme

@gotocva/react-markdown

A batteries-included React + Vite Markdown toolkit with two components:

  • <MarkdownRenderer /> — render Markdown to safe, styled HTML.
  • <MarkdownEditor /> — a Notion-style block editor that emits Markdown.

Highlights:

  • GitHub Flavored Markdown everywhere (tables, task lists, strikethrough, autolinks)
  • Syntax highlighting in both the editor (lowlight) and the renderer (rehype-highlight)
  • Slash command menu, inline bubble menu, language picker, resizable tables
  • Tables auto-numbered through an S.No column when present
  • Clean default CSS with light + dark scheme, easily themable via CSS variables
  • First-class TypeScript types
  • Ships ESM and UMD bundles plus declaration files

Built on top of react-markdown, remark-gfm, rehype-highlight, highlight.js, TipTap (ProseMirror), lowlight, marked and turndown.


Table of contents

  1. Install
  2. Quick start — renderer
  3. Quick start — editor
  4. Renderer API
  5. Editor API
  6. Slash command menu
  7. Inline bubble menu
  8. Code blocks & language picker
  9. Tables (with auto S.No)
  10. Markdown ↔ HTML utilities
  11. Styling and theming
  12. GFM support
  13. SSR (Next.js)
  14. Local development
  15. Publishing
  16. Troubleshooting
  17. License

Install

# npm
npm install @gotocva/react-markdown

# pnpm
pnpm add @gotocva/react-markdown

# yarn
yarn add @gotocva/react-markdown

react and react-dom (≥ 17) are peer dependencies. Install them if you don't have them yet:

npm install react react-dom

Quick start — renderer

import { MarkdownRenderer } from '@gotocva/react-markdown';
import '@gotocva/react-markdown/styles.css';

export default function Page() {
  return <MarkdownRenderer content="# Hello, world!" />;
}

That's the whole API for the common case. Pass a Markdown string in via content, or fetch from a URL via src:

<MarkdownRenderer src="/docs/getting-started.md" />

Source loaded through Vite? Use the ?raw import suffix:

import readme from './README.md?raw';
<MarkdownRenderer content={readme} />;

Quick start — editor

import { useState } from 'react';
import { MarkdownEditor } from '@gotocva/react-markdown';
import '@gotocva/react-markdown/styles.css';

export default function NotePage() {
  const [markdown, setMarkdown] = useState('# Untitled');

  return (
    <MarkdownEditor
      initialMarkdown={markdown}
      onChange={({ markdown }) => setMarkdown(markdown)}
      autoFocus
      minHeight="60vh"
    />
  );
}

The editor is uncontrolled internally (TipTap owns the document) — pass initialMarkdown once on mount, then sync your own state from onChange. Don't re-seed initialMarkdown on every render.

onChange receives both formats on every keystroke:

onChange?: (value: {
  markdown: string;
  html: string;
  editor: import('@tiptap/react').Editor;
}) => void;

Renderer API

<MarkdownRenderer
  content="..."                       // markdown string (wins over `src`)
  src="..."                           // url to fetch markdown from
  className="prose prose-slate"       // extra classes on the wrapping div
  style={{ maxWidth: 720 }}           // inline styles on the wrapping div
  gfm                                 // default: true
  allowHtml={false}                   // default: false; only allow if trusted
  syntaxHighlight                     // default: true
  highlightTheme="github"             // 'github' | 'github-dark' | 'atom-one-light' | 'atom-one-dark' | 'none'
  linkTarget="_blank"                 // adds rel="noopener noreferrer" automatically
  components={{ a: MyLink }}          // override any element renderer (react-markdown Components)
  loadingFallback={<p>Loading…</p>}   // shown while `src` is being fetched
  errorFallback={(e) => <p>{e.message}</p>}
/>

Renderer props

| Prop | Type | Default | Description | | ----------------- | ----------------------------------------------------------------------------- | ----------------- | ----------- | | content | string | undefined | Inline Markdown source. Wins over src if both are set. | | src | string | undefined | URL of a Markdown file to fetch and render. | | className | string | undefined | Extra classes on the wrapping <div>. | | style | React.CSSProperties | undefined | Inline style for the wrapping <div>. | | gfm | boolean | true | Enable GitHub Flavored Markdown. | | allowHtml | boolean | false | Allow raw HTML in the source. Only enable for trusted input. | | syntaxHighlight | boolean | true | Enable syntax highlighting for fenced code blocks. | | highlightTheme | 'github' \| 'github-dark' \| 'atom-one-dark' \| 'atom-one-light' \| 'none' | 'github' | highlight.js theme loaded on demand from jsDelivr. Use 'none' to bring your own. | | linkTarget | '_self' \| '_blank' \| '_parent' \| '_top' | undefined | target for rendered links. _blank auto-adds rel="noopener noreferrer". | | components | import('react-markdown').Components | undefined | Override or extend the underlying element renderer map. | | loadingFallback | ReactNode | Loading… | Shown while src is being fetched. | | errorFallback | (err: Error) => ReactNode | inline message | Shown when src fetch fails. |

import type { MarkdownRendererProps, HighlightTheme } from '@gotocva/react-markdown';

Editor API

<MarkdownEditor
  initialMarkdown="..."          // initial document as markdown
  initialHtml="..."              // alternative: initial document as html
  onChange={({ markdown, html, editor }) => { /* … */ }}
  placeholder="Type '/' for commands…"
  editable                       // default: true
  bubbleMenu                     // default: true
  slashCommand                   // default: true
  autoFocus                      // default: false
  minHeight="60vh"               // default: '200px'
  highlightTheme="github"        // default: 'github'
  className="my-editor"
/>

Editor props

| Prop | Type | Default | Description | | ----------------- | --------------------------------------------------------------------------------- | -------------------------------- | ----------- | | initialMarkdown | string | undefined | Initial document as Markdown. Converted to HTML on mount. | | initialHtml | string | undefined | Initial document as HTML (alternative to initialMarkdown). | | onChange | ({ markdown, html, editor }) => void | undefined | Fired on every document change. | | placeholder | string | "Type '/' for commands…" | Empty-line placeholder. | | editable | boolean | true | Read-only mode when false. | | bubbleMenu | boolean | true | Show the inline formatting bubble menu on selection. | | slashCommand | boolean | true | Enable the / slash command menu. | | autoFocus | boolean | false | Focus the editor on mount. | | className | string | undefined | Extra class applied to the editor wrapper. | | style | React.CSSProperties | undefined | Inline style for the wrapper. | | minHeight | string | '200px' | Min height of the editing surface. Any CSS length. | | highlightTheme | 'github' \| 'github-dark' \| 'atom-one-dark' \| 'atom-one-light' \| 'none' | 'github' | highlight.js theme for in-editor code highlighting. |

import type { MarkdownEditorProps } from '@gotocva/react-markdown';

Slash command menu

Press / at the start of a line (or anywhere in a paragraph) to open a Notion-style block picker. Type to filter, arrow-key to navigate, Enter to confirm, Esc to dismiss.

| Item | Shortcut to type | Result | | --------------- | ---------------- | ------ | | Text | text | Plain paragraph | | Heading 1 / 2 / 3 | h1 / h2 / h3 | Section headings | | Bullet list | bullet, ul | Unordered list | | Numbered list | numbered, ol | Ordered list | | To-do list | todo, task | List with checkboxes | | Quote | quote | Block quote | | Code block | code | Fenced code — opens a language picker (see below) | | Table | table | 4-column table with an auto-numbered S.No column | | Divider | divider, hr | Horizontal rule | | Image | image | Insert an image by URL |

Markdown input shortcuts

These trigger as you type — no slash menu required:

| Type this | Becomes | | ------------------------ | -------------------- | | # ### | Heading 1 / 2 / 3 | | - / * | Bullet list item | | 1. | Numbered list item | | [ ] | Task list item | | > | Block quote | | ``` then Enter | Code block (paste a language right after the fence) | | --- | Horizontal rule | | **bold** | bold | | *italic* / _italic_ | italic | | ~~strike~~ | ~~strike~~ | | `code` | code |


Inline bubble menu

Select any text and a small toolbar appears above the selection:

  • B — bold (⌘ / Ctrl+B)
  • I — italic (⌘ / Ctrl+I)
  • ~~S~~ — strikethrough
  • </> — inline code (⌘ / Ctrl+E)
  • — link (opens a prompt() for the URL)

The bubble menu hides when an image or horizontal-rule node is selected, and yields to the table toolbar (below) when the caret is in a table without an active text selection.


Code blocks & language picker

When you pick Code block from the slash menu, a searchable language picker opens next to the cursor:

  1. Type to filter (ts, python, rust, …)
  2. / to navigate
  3. Enter to confirm — Esc inserts a plain block

What you get:

  • Live in-editor highlighting via lowlight (preloaded with the common highlight.js language set — ~30 languages).
  • A small language label in the top-right corner of the code block (Notion-style, driven by a data-language attribute).
  • GFM fence in the Markdown output (e.g. ```typescript), so re-rendering through <MarkdownRenderer /> re-highlights the same block using rehype-highlight.

Supported language IDs include: typescript, javascript, python, java, c, cpp, csharp, go, rust, ruby, php, swift, kotlin, bash, shell, sql, json, yaml, xml, css, scss, less, markdown, graphql, diff, r, lua, perl, objectivec, makefile, plaintext.

To use a custom highlight theme, set highlightTheme="none" on the editor (and/or renderer) and import a stylesheet of your own:

import 'highlight.js/styles/atom-one-dark.css';

Tables (with auto S.No)

Type /table to insert a 4-column table whose first column is S.No, pre-filled with 1, 2, 3 ….

Floating toolbar (appears above the table when the caret is inside it):

| Button | Action | | ----------- | ----------------------------------- | | ↑+ / ↓+ | Add row above / below the current cell | | ↕− | Delete the current row | | ←+ / →+ | Add column before / after the current cell | | ↔− | Delete the current column | | Hdr | Toggle header row | | | Delete the table |

Other niceties:

  • Resizable columns — drag the right edge of any column.
  • Keyboard navigationTab / Shift+Tab move between cells. Tab in the last cell adds a new row.
  • GFM round-trip — emitted as a standard | ... | markdown table, so it renders identically through <MarkdownRenderer /> and on GitHub.

Auto-numbering rules

Any table whose first header cell reads S.No, Sno, Serial, Serial No, Serial Number, or # (case- and whitespace-insensitive) is treated as a serial column. The auto-fill plugin:

  • Re-numbers data rows on every row add / delete / reorder.
  • Skips cells with custom text — only cells that are empty or already a plain integer (/^\d+$/) are rewritten. So "1.", "first", or formatted serials are left alone.
  • Skips cells with non-default shape — cells with lists, multiple paragraphs, or block content are untouched.
  • Opt out by renaming or clearing the S.No header.

Markdown ↔ HTML utilities

The conversion functions used internally by the editor are exported too:

import { markdownToHtml, htmlToMarkdown } from '@gotocva/react-markdown';

const html = markdownToHtml('# Hello **world**');
const md = htmlToMarkdown(html); // round-trips cleanly, including GFM tables

htmlToMarkdown is hardened against the TipTap-specific HTML quirks that otherwise break GFM table conversion:

  • Strips <colgroup> children from <table> (otherwise turndown's isFirstTbody check fails and the table falls through as raw HTML).
  • Unwraps <p> elements that are direct children of <th> / <td> so cell text stays on one line.

Styling and theming

Everything is scoped under .gotocva-markdown (renderer) and .gotocva-md-editor (editor) so importing the stylesheet cannot leak into the host application.

CSS variables (renderer)

.gotocva-markdown {
  --gmd-fg: #111827;
  --gmd-link: #6366f1;
  --gmd-border: #e5e7eb;
  --gmd-bg-subtle: #f9fafb;
  --gmd-bg-code: #f3f4f6;
  --gmd-radius: 8px;
  --gmd-font-sans: 'Inter', system-ui, sans-serif;
  --gmd-font-mono: 'JetBrains Mono', monospace;
}

The library auto-switches a dark palette through @media (prefers-color-scheme: dark). Force a scheme by setting data-theme="light" on the wrapping element.

Custom element renderers (renderer)

import { MarkdownRenderer, type Components } from '@gotocva/react-markdown';

const components: Components = {
  h1: ({ children }) => <h1 className="my-fancy-h1">{children}</h1>,
  a: ({ href, children }) => (
    <a href={href} className="my-link" target="_blank" rel="noopener noreferrer">
      {children}
    </a>
  ),
};

<MarkdownRenderer content={md} components={components} />;

Skipping the default stylesheet

Don't import @gotocva/react-markdown/styles.css and bring your own CSS for the .gotocva-markdown / .gotocva-md-editor scopes.


GFM support

GFM is enabled by default in both the renderer (via remark-gfm) and the editor's Markdown I/O (via marked's GFM mode + turndown-plugin-gfm), so all of the following round-trip cleanly:

  • Tables (| a | b |)
  • Strikethrough (~~text~~)
  • Task lists (- [x] done, - [ ] todo)
  • Autolinks (bare URLs like https://example.com)
  • Fenced code with language hints

Disable in the renderer with gfm={false} for strict CommonMark semantics. The editor always supports GFM.


SSR (Next.js)

The components are client-only — they touch document to inject the highlight.js theme stylesheet, and TipTap depends on DOM APIs. In Next.js App Router:

// app/note/editor.tsx
'use client';
import { MarkdownEditor } from '@gotocva/react-markdown';
// …

In Pages Router or anywhere you need a hard SSR boundary:

import dynamic from 'next/dynamic';

const MarkdownEditor = dynamic(
  () => import('@gotocva/react-markdown').then((m) => m.MarkdownEditor),
  { ssr: false },
);

The renderer also injects its highlight.js theme through a client effect, so an SSR-rendered page that uses only <MarkdownRenderer /> works without the 'use client' directive — the theme stylesheet appears once the page hydrates.


Local development

git clone https://github.com/gotocva/react-markdown
cd react-markdown
npm install

# Launch the demo / playground (Vite dev server)
npm run dev
# → http://localhost:5173

# Type-check
npm run lint

# Production build of the library (dist/)
npm run build

# Production build of the demo (demo-dist/)
npm run build:demo

# Preview the production demo build
npm run preview

Project layout

@gotocva/react-markdown/
├── src/                                # Library source (published)
│   ├── index.ts                        # Public exports
│   ├── components/
│   │   ├── MarkdownRenderer.tsx
│   │   └── MarkdownEditor/
│   │       ├── MarkdownEditor.tsx
│   │       ├── EditorBubbleMenu.tsx
│   │       ├── TableMenu.tsx
│   │       ├── slash-command/
│   │       ├── language-picker/
│   │       └── serial-number/
│   ├── styles/
│   │   ├── markdown.css
│   │   └── editor.css
│   └── utils/
│       ├── markdown.ts                 # markdown ↔ html
│       └── useHighlightTheme.ts
├── demo/                               # Playground app (not published)
├── index.html
├── vite.config.ts
├── tsconfig.json / .build.json / .node.json
└── package.json

Publishing

prepublishOnly runs the production build automatically, so a typical release is just:

npm version patch | minor | major
npm publish

publishConfig.access = "public" is set in package.json so the scoped package publishes publicly without needing --access public.

Verify what will go to the registry before publishing:

npm pack --dry-run

Only dist/, README.md, and LICENSE are packaged (controlled by the files field).


Troubleshooting

Nothing renders. Pass either content or src to MarkdownRenderer, or initialMarkdown / initialHtml to MarkdownEditor.

My styles look unstyled. Import the stylesheet once at the top of your app:

import '@gotocva/react-markdown/styles.css';

Syntax highlighting doesn't appear. Confirm syntaxHighlight isn't false, and highlightTheme isn't 'none'. The theme stylesheet is fetched from https://cdn.jsdelivr.net/npm/highlight.js. If you're offline or behind a strict CSP, set highlightTheme="none" and import a highlight.js theme locally.

Raw HTML in my Markdown is not rendered. For security, raw HTML is stripped from the renderer by default. Pass allowHtml if (and only if) the source is trusted.

A table shows up as code in the live preview. Already fixed — but worth knowing why: TipTap emits <colgroup> and wraps cell text in <p>, both of which break turndown's GFM table rule. htmlToMarkdown pre-processes the HTML to remove <colgroup> and unwrap cell paragraphs.

TypeScript can't find the types. Make sure you imported from @gotocva/react-markdown (not a deep path) and that "moduleResolution": "bundler" (or "node16" / "nodenext") is set in your tsconfig.json.

SSR errors about document not defined. See the SSR section.


License

MIT © gotocva

Powered by the unified / remark / rehype and ProseMirror / TipTap ecosystems.