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

markdown-text-editor

v1.9.0

Published

A markdown editor that enhances your textarea instead of replacing it, so form submission, validation and required fields keep working. WYSIWYG hybrid mode, live preview, find and replace, RTL, TypeScript types. Works with any backend.

Readme

Markdown Text Editor

Toolbar, live preview and WYSIWYG on the textarea you already have

npm installs Jsdelivr hits Latest Release License Secured GitHub Repo stars

A lightweight, embeddable JavaScript Markdown editor that transforms a standard HTML <textarea> into a full-featured editing experience — without breaking native form submission. Works with any backend (Django, Laravel, PHP, Node.js, Rails) out of the box.

A native-first Markdown editor built on a standard textarea. No data binding, no API — just drop it in and your forms keep working as-is.

Works standalone. No Frutjam, no Tailwind, no framework required, the styles are bundled.

Hybrid mode: headings, bold text and list markers styled live inside the textarea while the Markdown syntax stays visible

Hybrid mode. Text is styled as you type and the Markdown stays where it is, because this is your <textarea>, not a copy of it.

No complex APIs. No data binding. No JSON schemas. Just a <textarea> that types Markdown and submits like any normal form field — enhanced with a rich toolbar, live preview, and WYSIWYG hybrid mode.

⭐ Why developers choose this over EasyMDE / SimpleMDE

Most JavaScript markdown editors (EasyMDE, SimpleMDE, CodeMirror-based editors) hide your <textarea> and edit a copy, writing the value back when the form is submitted. That holds up until something else needs the field: a required input the browser cannot focus blocks the submit entirely, and .value or FormData read before submit returns an empty string, which breaks htmx, Turbo, Livewire, autosave and unsaved-changes guards.

MarkdownEditor is different. It sits transparently on top of your existing <textarea>:

| Feature | MarkdownEditor | EasyMDE / SimpleMDE | |---|---|---| | Native <textarea> preserved | ✅ | ❌ Hidden, edited as a copy | | Works with required fields | ✅ | ❌ Browser blocks the submit | | .value correct before submit | ✅ | ❌ Empty until the form submits | | Serialises with FormData, htmx, Turbo | ✅ | ❌ Needs the editor's own API | | WYSIWYG hybrid mode | ✅ | ❌ | | Inline event handlers (CSP) | ✅ None | Some | | RTL support | ✅ Built in | Via CodeMirror's direction option | | Built-in Find & Replace | ✅ | ❌ | | Swap the markdown parser | ✅ | ❌ | | Keyboard shortcuts | ✅ | Partial | | Dark mode / theming | ✅ | Limited | | Bundle size, gzipped | 54 KB | 107 KB (JS + CSS) |

🚀 Quick Start

NPM (bundlers: Vite, webpack, Rollup, etc.)

npm install markdown-text-editor
import MarkdownEditor from 'markdown-text-editor';
new MarkdownEditor('#markdown-editor');

CDN: ES module

<script type="module">
  import MarkdownEditor from 'https://cdn.jsdelivr.net/npm/markdown-text-editor/dist/markdown-text-editor.es.js';
  new MarkdownEditor('#markdown-editor');
</script>

CDN: global script tag (IIFE)

<form method="post" action="/submit">
  <textarea id="markdown-editor" name="content"># Hello World</textarea>
  <button type="submit">Save</button>
</form>

<script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script>
<script>
  new MarkdownEditor('#markdown-editor');
</script>

Via CDN: no import needed, MarkdownEditor is available globally.

That's it. Form submission, .value access, and all native textarea behaviour work exactly as before.

✨ Features

  • 🔌 Native Form Integration — Works exactly like a standard <textarea>. No complex APIs — just use .value or the name attribute. Compatible with Django, Laravel, PHP, Rails, Node.js, and with React or Vue via a ref and destroy() on unmount
  • 🔀 WYSIWYG Hybrid Mode — Renders bold, italic, headings, and code live as you type while keeping the underlying Markdown. Switch to plain mode for raw syntax editing
  • ⚡ Live Preview — Full side-by-side Markdown preview with clickable task list checkboxes that sync back to the source instantly
  • 🔧 Bring Your Own Renderer — Swap marked for markdown-it or any other parser so the preview matches whatever your backend renders. The sanitizer is replaceable too, and DOMPurify still runs by default
  • 🏷️ Variable Dropdown — Give template authors a menu of readable names that insert placeholders like {{customer.name}}. Entries can be grouped, and a sample value can stand in for the placeholder in the preview
  • 🖼️ Advanced Image Upload — Upload images directly to your server or S3. Avoids heavy Base64 strings for better performance and SEO
  • 🔍 Find & Replace — Built-in panel (Ctrl+F / Ctrl+H) with live match counter, next/prev navigation, case-sensitive toggle, and replace all
  • ⌨️ Keyboard Shortcuts — Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+Z, Ctrl+1–Ctrl+3 for headings, and more
  • 📝 Smart List Continuation — GitHub-style: press Enter inside a list and the bullet/number continues automatically
  • 🔄 Undo / Redo — Full diff-based history with exact cursor restoration. Works with Ctrl+Z, Ctrl+Y, Ctrl+Shift+Z
  • ♿ Accessible by Default — role="toolbar", aria-pressed, aria-disabled, disabled, screen-reader-friendly SVGs, and correct focus restoration on modal close
  • 🛡️ XSS Safe — Preview output sanitized via DOMPurify before rendering, including the output of a custom renderer
  • 🛡️ No inline scripts — No inline event handlers and no eval, so script-src needs no exception. The stylesheet is currently injected as a <style> element, which a strict style-src blocks (#42)
  • 🌍 RTL Support — Native Right-to-Left support for Arabic, Urdu, Farsi, and other RTL languages
  • 🌙 Dark Mode & Theming — Inherits data-theme from any ancestor element. Built-in light, dark, snowberry, and darkberry themes. Fully customizable via CSS variables
  • 🧩 Custom Tools — Add your own toolbar button with { custom: { title, icon, action } }, or extend MarkdownEditor.Tool when it builds its own markup. Keyboard shortcuts, translation and teardown work exactly as they do for the built-ins. No fork, no patching
  • 🎛️ Modular Toolbar — Pick exactly which tools appear and in what order
  • 🟦 TypeScript Ready — Definitions ship with the package. Options, toolbar entries and variable shapes are all checked, so a mistyped tool name is a compile error rather than a silently missing button
  • 📦 Universal Module Support — ESM, CommonJS, UMD, and IIFE. Works with Vite, webpack, Rollup, or directly via <script src> CDN — no configuration needed
  • 🚀 High Performance — ~54KB gzipped (253KB minified). Debounced preview, cached layout calculations, conflict-free Tab/Enter handling for large documents

🛠 Developer Workflow

Getting & Setting Content

// Get — just like any textarea
const markdown = document.getElementById('markdown-editor').value;

// Set — editor UI updates automatically
document.getElementById('markdown-editor').value = '## Updated content';

React to every change with onChange

const editor = new MarkdownEditor('#markdown-editor', {
    onChange(value) {
        console.log('Content changed:', value.length, 'characters');
    }
});

Auto-save draft to localStorage

const textarea = document.getElementById('markdown-editor');
const saved = localStorage.getItem('draft');
if (saved && !textarea.value) textarea.value = saved;

const editor = new MarkdownEditor('#markdown-editor', {
    onChange(value) {
        localStorage.setItem('draft', value);
    }
});

Add your own toolbar button

Most tools are just a title, an icon and a function. No class, no fork:

new MarkdownEditor('#markdown-editor', {
    toolbar: ['bold', 'italic', {
        custom: {
            title: 'Callout',            // tooltip, and the .callout-btn class
            icon: '<svg>…</svg>',
            shortcut: 'Ctrl+Shift+C',    // optional, shown in the tooltip
            action(editor) {
                editor.insertText('> [!NOTE]\n> ');
            }
        }
    }, 'preview']
});

Use editor.insertText(text, caretOffset, trailingLength) rather than writing to the textarea yourself — it replaces the selection, restores focus, moves the caret, re-renders the preview, fires onChange, and keeps undo working.

A tool that builds its own markup, such as a dropdown or a dialog, extends the base class instead:

class EmbedTool extends MarkdownEditor.Tool {
    constructor(editor) {
        super(editor, 'Embed video');
        this.button = this.createButton('<svg>…</svg>');
    }
    applySyntax(event) {
        // MarkdownEditor.modal is the same dialog the link and image tools use
        const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', '…', 'Embed');
    }
}

new MarkdownEditor('#markdown-editor', { toolbar: ['bold', EmbedTool, 'preview'] });

Two helpers exist for tools that need them: editor.renderMarkdown(md) returns sanitized HTML exactly as the preview does, and editor.createToolbar(textarea, ['bold', 'italic']) builds a real toolbar bound to another textarea, for a tool collecting rich text in its own dialog.

Tear down in SPAs

// Removes editor UI, restores original textarea, cleans up all event listeners
editor.destroy();

In React, create the editor in an effect and return destroy as the cleanup — that also covers StrictMode running effects twice in development:

useEffect(() => {
    const editor = new MarkdownEditor(ref.current, { onChange });
    return () => editor.destroy();
}, []);

Use defaultValue, not value. The editor writes to the textarea directly, so a controlled binding (or Vue's :value) would overwrite what the user is typing. Full React and Vue examples are in the documentation.

The same two rules cover Svelte, Angular and anything else: don't bind the value, and call destroy() on unmount. In Angular, onChange fires outside the zone, so wrap it in zone.run() for change detection to notice.

Options are read once when the editor is constructed. Changing them later has no effect — destroy the editor and create a new one instead.

📖 Documentation

Full API reference, configuration options, theming guide, and advanced image upload docs: 👉 frutjam.com/plugins/markdown-editor

WYSIWYG Hybrid Mode vs Plain Mode

Hybrid Mode — live formatting as you type

Hybrid mode WYSIWYG styled markdown editor

Plain Mode — raw Markdown syntax

Plain mode markdown editor


🤝 Contributing

Contributions are welcome! Bug fixes, feature requests, and improvements — open an issue or submit a pull request.

See CONTRIBUTING.md for setup, tests, project layout, and how to add a toolbar tool.


License

MIT License


⭐ Support

If this saves you time, consider giving it a star — it helps others find this project!

GitHub stars