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

pd-editor-vue

v2.0.0

Published

Vue 3 Markdown editor for technical content and AI writing workflows

Readme

pd-editor-vue

npm Demo Vue 3 TypeScript License

A Vue 3 Markdown editor for technical content and AI writing workflows, with CodeMirror 6, live Mermaid and math preview, frontmatter, linting, image upload, and a composable API.

Use it when you need a production-ready Markdown field without hand-assembling parser, preview, styling, shortcuts, plugins, and CodeMirror integration yourself.

Try the interactive demo: laochen1994.github.io/pd-markdown-editor.

Highlights

  • 💚 Vue 3 component - v-model friendly, typed props, save event.
  • 🚀 CodeMirror 6 core - fast editing, Markdown language support, history, search, folding, autocomplete.
  • 👀 Preview modes - edit, split, and preview.
  • 🎨 Styled Markdown preview - powered by pd-markdown + pd-markdown-ui/vue.
  • 🧠 Markdown-aware typing - smart Enter, Tab, and Shift+Tab behavior for common Markdown blocks.
  • 🧰 Toolbar included - default toolbar, custom toolbar, plugin toolbar items.
  • 🖼️ Image upload plugin - paste/drag images and replace upload placeholders with final URLs.
  • 🧭 TOC plugin - live heading navigation with stable parser-generated ids.
  • 🧩 Composable escape hatch - useMarkdownEditor for fully custom Vue layouts.
  • 🧱 Headless entry - manual CSS control for design systems.
  • 🌓 Light/dark themes - editor and preview stay visually aligned.

Install

pnpm add pd-editor-vue
# npm install pd-editor-vue
# yarn add pd-editor-vue

vue is a peer dependency. The adapter installs pd-editor-core and includes the default preview styles.

Quick Start

<script setup lang="ts">
import { ref } from "vue";
import { MarkdownEditor } from "pd-editor-vue";

const content = ref("# Hello pd-editor-vue");
</script>

<template>
  <MarkdownEditor
    v-model="content"
    theme="light"
    preview="split"
    :height="640"
    placeholder="Write Markdown..."
  />
</template>

The default entry imports base preview styles automatically:

import { MarkdownEditor } from "pd-editor-vue";

For full CSS control, use the headless entry:

import { MarkdownEditor } from "pd-editor-vue/headless";
import "pd-editor-vue/styles.css";

Feature Matrix

| Area | Support | | --- | --- | | Component mode | v-model, defaultValue | | Preview | edit, split, preview | | Styling | Styled root entry, headless entry, explicit styles.css | | Markdown UI | Headings, paragraphs, lists, task lists, code, table, blockquote, heading ids | | Toolbar | Built-in toolbar, custom toolbar items, plugin toolbar items | | Plugins | imageUploadPlugin, tocPlugin, custom plugins | | Commands | Formatting, headings, lists, quote, code, link, image, table, horizontal rule | | Runtime controls | Reactive theme, readOnly, modelValue sync | | Advanced | CodeMirror extensions, custom preview component map |

Fenced code previews render lightweight semantic <pre><code> markup by default. Override renderComponentMap.code when your application needs a specific syntax highlighter.

Props & Events

| Prop | Type | Default | Description | | --- | --- | --- | --- | | modelValue | string | - | Controlled Markdown value via v-model | | defaultValue | string | "" | Initial value for uncontrolled usage | | theme | "light" \| "dark" | "light" | Editor and preview theme | | placeholder | string | - | CodeMirror placeholder | | readOnly | boolean | false | Prevent user and command edits | | height | string \| number | "500px" | Editor container height | | preview | "edit" \| "preview" \| "split" | "edit" | View mode | | toolbar | boolean \| ToolbarItem[] | true | Built-in toolbar, hidden toolbar, or custom items | | plugins | EditorPlugin[] | [] | Core plugins | | extensions | Extension[] | [] | CodeMirror 6 extensions | | codeLanguages | MarkdownCodeLanguages | - | Optional fenced code language resolver for the editor | | renderComponentMap | Record<string, unknown> | - | Override Markdown preview components |

| Event | Payload | Description | | --- | --- | --- | | update:modelValue | string | Emitted when content changes | | save | string | Emitted by Ctrl/Cmd+S |

Preview Modes

<MarkdownEditor v-model="content" preview="edit" />
<MarkdownEditor v-model="content" preview="split" />
<MarkdownEditor :model-value="content" preview="preview" />

split mode keeps editor and preview side-by-side. preview mode renders Markdown without mounting the editor.

Keyboard Shortcuts

| Shortcut | Action | | --- | --- | | Ctrl/Cmd+B | Bold | | Ctrl/Cmd+I | Italic | | Ctrl/Cmd+K | Link | | Ctrl/Cmd+Shift+X | Strikethrough | | Ctrl/Cmd+Alt+1 | Heading 1 | | Ctrl/Cmd+Alt+2 | Heading 2 | | Ctrl/Cmd+Alt+3 | Heading 3 | | Ctrl/Cmd+Shift+7 | Ordered list | | Ctrl/Cmd+Shift+8 | Bullet list | | Ctrl/Cmd+Shift+9 | Quote | | Ctrl/Cmd+S | save event | | Enter | Continue list/task/ordered/quote block | | Tab | Indent Markdown block | | Shift+Tab | Outdent Markdown block |

Toolbar Commands

The default toolbar includes:

bold, italic, strikethrough, heading1, heading2, heading3, unorderedList, orderedList, taskList, link, image, quote, code, codeBlock, table, horizontalRule.

Disable it:

<MarkdownEditor v-model="content" :toolbar="false" />

Custom toolbar:

<script setup lang="ts">
import type { ToolbarItem } from "pd-editor-core";

const toolbar: ToolbarItem[] = [
  { command: "bold", label: "Bold", icon: "<strong>B</strong>", shortcut: "Ctrl+B" },
  { command: "heading2", label: "H2", icon: "<span>H2</span>" },
  { command: "divider", label: "", icon: "", divider: true },
  { command: "link", label: "Link", icon: "<span>🔗</span>", shortcut: "Ctrl+K" },
];
</script>

<template>
  <MarkdownEditor v-model="content" :toolbar="toolbar" />
</template>

Custom toolbar icon values are trusted HTML from your application code. Do not pass untrusted user content into icon.

Plugins

Image Upload

<script setup lang="ts">
import { computed, ref } from "vue";
import { MarkdownEditor } from "pd-editor-vue";
import { imageUploadPlugin } from "pd-editor-core";

const content = ref("");

const plugins = computed(() => [
  imageUploadPlugin({
    maxSize: 5 * 1024 * 1024,
    handler: async (file) => {
      const form = new FormData();
      form.append("file", file);

      const response = await fetch("/api/upload", {
        method: "POST",
        body: form,
      });

      const data = await response.json() as { url: string };
      return data.url;
    },
  }),
]);
</script>

<template>
  <MarkdownEditor
    v-model="content"
    :plugins="plugins"
    preview="split"
  />
</template>

Table Of Contents

<script setup lang="ts">
import { nextTick, onMounted, ref } from "vue";
import { MarkdownEditor } from "pd-editor-vue";
import { tocPlugin, type EditorPlugin } from "pd-editor-core";

const content = ref("# Intro");
const tocRef = ref<HTMLElement | null>(null);
const plugins = ref<EditorPlugin[]>([]);

onMounted(async () => {
  await nextTick();
  if (tocRef.value) {
    plugins.value = [tocPlugin({ container: tocRef.value, maxLevel: 3 })];
  }
});
</script>

<template>
  <div style="display: grid; grid-template-columns: 1fr 220px; gap: 16px">
    <MarkdownEditor v-model="content" :plugins="plugins" preview="split" :height="640" />
    <aside ref="tocRef" />
  </div>
</template>

Custom Preview Rendering

Vue preview uses tag-style component keys compatible with pd-markdown-ui/vue.

<script setup lang="ts">
import CustomBlockquote from "./CustomBlockquote.vue";

const renderComponentMap = {
  blockquote: CustomBlockquote,
};
</script>

<template>
  <MarkdownEditor
    v-model="content"
    preview="split"
    :render-component-map="renderComponentMap"
  />
</template>

Common keys include h1, h2, h3, p, ul, ol, li, blockquote, table, thead, tbody, tr, th, td, pre, and code.

Raw HTML Markdown nodes are not rendered by the default preview. If your application needs embedded HTML, sanitize it before rendering it through a custom preview pipeline.

Composable API

Use useMarkdownEditor when you want to build your own Vue shell around the core editor:

<script setup lang="ts">
import { useMarkdownEditor } from "pd-editor-vue";

const { containerRef, getValue, executeCommand } = useMarkdownEditor({
  initialValue: "# Custom shell",
  onChange: (value) => console.log(value),
});

const logValue = () => {
  console.log(getValue());
};
</script>

<template>
  <button type="button" @click="executeCommand('bold')">Bold</button>
  <button type="button" @click="logValue">Log</button>
  <div ref="containerRef" style="height: 500px" />
</template>

Initialization options are read when the editor mounts. Structural options such as initialValue, placeholder, toolbar, plugins, extensions, and codeLanguages are not rebuilt automatically. Recreate the composable owner when those structural options need to change.

SSR / Nuxt

The editor requires a browser DOM. In SSR frameworks, mount it from a client-only boundary.

<ClientOnly>
  <MarkdownEditor v-model="content" preview="split" />
</ClientOnly>

FAQ

Do I need to import CSS?

If you import pd-editor-vue, base preview styles are included automatically. If you import pd-editor-vue/headless, import pd-editor-vue/styles.css yourself.

Can I use v-model?

Yes. v-model is the recommended component API. External modelValue updates are synchronized into the editor without emitting update:modelValue again. Use defaultValue for one-time uncontrolled initial content.

Can I hide preview or toolbar?

Yes. Use preview="edit" and :toolbar="false".

Can I customize Markdown preview components?

Yes. Use renderComponentMap with tag-style keys such as h1, p, blockquote, table, th, and td.

Can I access the underlying editor?

Use useMarkdownEditor for direct access to the core editor ref. The component API is intentionally higher-level.

Does this work for admin/CMS apps?

Yes. It is designed for repeated product workflows: CMS fields, docs portals, changelog editors, support dashboards, knowledge bases, and AI writing tools.

Related Packages

  • pd-editor-core - Framework-agnostic CodeMirror editor core.
  • pd-editor-react - React adapter.
  • pd-markdown - Markdown parser/renderer.
  • pd-markdown-ui - Styled Markdown preview primitives.

License

MIT