pd-editor-vue

Vue 3 Markdown editor for technical content and AI writing workflows
1,942
Install
npm install pd-editor-vue
Latest Version:2.3.3
Vue:3
License:MIT
Last Updated:Sep 4, 2026
Links: GitHub  ·  npm
Maintainer: my-bad-egg

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
maxLength number - Maximum document length
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
labels EditorLabels - Toolbar labels keyed by command
messages EditorMessages enUS Built-in UI and plugin messages; supports runtime updates
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

modelValue, theme, readOnly, maxLength, preview, toolbar, labels, and messages update after mount. placeholder, plugins, extensions, and codeLanguages are initialization options.

<script setup>
import { MarkdownEditor, zhCN } from "pd-editor-vue";
</script>

<template>
  <MarkdownEditor :messages="zhCN" />
</template>

Component Ref And Export Actions

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

const editorRef = ref<MarkdownEditorHandle | null>(null);

const exportNotes = async () => {
  await editorRef.value?.copyMarkdown();
  await editorRef.value?.copyHtml();
  editorRef.value?.downloadMarkdown("notes.md");
};
</script>

<template>
  <MarkdownEditor ref="editorRef" v-model="content" preview="split" />
</template>

The exposed ref also provides getValue(), getCharacterCount(), and focus(). copyHtml() copies the currently mounted preview HTML.

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:

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

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, { signal }) => {
      const form = new FormData();
      form.append("file", file);

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

      const data = await response.json() as { url: string };
      return data.url;
    },
    onStatusChange: ({ status, progress, cancel, retry }) => {
      console.log(status, progress, cancel, retry);
    },
  }),
]);
</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>

Composable options are read when its owner mounts. Recreate the composable owner when initialization options need to change; use the component when reactive prop updates are required.

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.

  • 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

Related Packages

nbeditor

A reusable Vue 3 markdown editor: CodeMirror 6 editing, live preview, Mermaid di...

631
fo-markdown-note

**fo-markdown-note** is a **Vue.js** component that provides a simple Markdown e...

1,025 1
@itguy614/clean-ui-editor

A markdown editor for clean-ui — CodeMirror 6 buffer, hidden syntax markers, ext...

885 0
markweave

Markdown-first WYSIWYG editor built on Tiptap and CodeMirror, providing Typora-l...

11,374 1

Version History

Version Released Vue License
2.3.3 ^3.3.0 || ^3.4.0 || ^3.5.0 MIT
2.3.2 ^3.3.0 || ^3.4.0 || ^3.5.0 MIT
2.3.1 ^3.3.0 || ^3.4.0 || ^3.5.0 MIT
2.3.0 ^3.3.0 || ^3.4.0 || ^3.5.0 MIT
2.1.0 ^3.3.0 || ^3.4.0 || ^3.5.0 MIT