@tiaohsun/vue-rich-text-editor

A thin, batteries-included Vue 3 wrapper around Tiptap v3 — editor component, toolbar, bubble menus, i18n, HTML/JSON output.
1,299
Install
npm install @tiaohsun/vue-rich-text-editor
Latest Version:0.5.0
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: tiaohsun

@tiaohsun/vue-rich-text-editor

English · 繁體中文

A thin, batteries-included Vue 3 wrapper around Tiptap v3.

Every feature in this package comes from Tiptap. We only bundle the official extensions into a ready-to-use editor component with a toolbar and bubble menus, so you can drop in a rich text editor without wiring everything up yourself.

There is intentionally no separate technical documentation. For anything beyond what is shown here — adding nodes/marks, custom commands, keyboard shortcuts, node views, schema, etc. — use Tiptap directly and read the official Tiptap docs. This package does not invent its own plugin system; you extend it the exact same way you extend any Tiptap editor.


Features

All provided by Tiptap, pre-wired here:

  • Headings, bold/italic/underline/strike, blockquote, lists, code & code block, horizontal rule
  • Links (with a link bubble menu)
  • Tables (insert/merge/split via a table bubble menu)
  • Images — resizable & alignable node view, with an image bubble menu
  • YouTube embeds — resizable & alignable node view, with a video bubble menu
  • Text alignment, text color, font family, font size, highlight
  • Collapsible blocks via the official Details extension (native <details>)
  • Placeholder, and Ctrl/Cmd+Shift+Enter to exit a blockquote/table
  • A configurable toolbar and text/table/image/video/link bubble menus
  • Output as HTML or Tiptap JSON

Optional (opt-in) extensions: image upload handler, text lightbox, image lightbox.


Install

npm i @tiaohsun/vue-rich-text-editor
# or: pnpm add @tiaohsun/vue-rich-text-editor

Peer dependencies you must also install (kept as a single shared copy to avoid duplicate ProseMirror/Tiptap instances):

npm i vue@^3.5 @tiptap/core@^3 @tiptap/pm@^3 @tiptap/vue-3@^3

Quick start

<script setup lang="ts">
import { ref } from 'vue'
import { RichTextEditor } from '@tiaohsun/vue-rich-text-editor'
import '@tiaohsun/vue-rich-text-editor/styles'

const content = ref('') // HTML string by default
</script>

<template>
  <RichTextEditor v-model="content" placeholder="Start typing…" />
</template>

Store as Tiptap JSON instead of HTML:

<RichTextEditor v-model="doc" output-format="json" />

Read-only rendering (e.g. to display saved content):

<RichTextEditor :model-value="content" readonly />

<RichTextEditor> props

Prop Type Default Description
modelValue string | JSONContent | null '' v-model. HTML string, or Tiptap JSON when output-format="json".
outputFormat 'html' | 'json' 'html' What update:modelValue emits.
extensions Extensions [] Extra Tiptap extensions appended to the defaults.
resolveExtensions (ctx) => Extensions — Fully replace the default extension set.
toolbarItems ToolbarItem[] defaults Override the toolbar (see below).
placeholder string — Placeholder text.
readonly boolean false Disable editing; hides the toolbar and bubble menus.
stickyToolbar boolean true Pin the toolbar to the top of the scroll container (see below).

Sticky toolbar

The toolbar stays pinned to the top of whatever scrolls — the page, or the nearest scrollable ancestor (a modal body, a scrollable panel). This keeps it reachable in long documents, matching CKEditor 5's classic editor and TinyMCE's toolbar_sticky.

If your page has a position: fixed / sticky header that would cover the toolbar, set the offset via CSS — the same idea as CKEditor's ui.viewportOffset.top and TinyMCE's toolbar_sticky_offset, and like both of those it defaults to 0:

.my-editor-wrapper {
	--rte-toolbar-offset: 64px;
}

Pass :sticky-toolbar="false" to opt out entirely.

Requires that no ancestor between the scroll container and the editor uses overflow: hidden — that would trap the sticky element inside it. overflow: clip and overflow: auto/scroll are both fine.


Extending (the Tiptap way)

extensions is just an array of native Tiptap extensions appended to the built-in set:

<script setup lang="ts">
import { RichTextEditor } from '@tiaohsun/vue-rich-text-editor'
import MyExtension from './MyExtension' // a normal Node/Mark/Extension.create(...)
</script>

<template>
  <RichTextEditor v-model="content" :extensions="[MyExtension]" />
</template>

Need full control over the default set? Use resolveExtensions:

import { createDefaultExtensions } from '@tiaohsun/vue-rich-text-editor'

const resolve = () => [
  ...createDefaultExtensions({ placeholder: '…' }),
  // add / remove / reorder as you like
]

Writing the extension itself (nodes, marks, commands, node views) is plain Tiptap — see the Tiptap docs.


Optional extensions

Imported from subpaths so they stay out of the core bundle until you opt in.

Image upload

import { ImageUploadExtension, imageUploadToolbarItem } from '@tiaohsun/vue-rich-text-editor/extensions/image-upload'
import type { ImageUploadHandler } from '@tiaohsun/vue-rich-text-editor/extensions/image-upload'

const upload: ImageUploadHandler = async (file) => {
  const url = await uploadSomewhere(file) // your storage (R2, S3, …)
  return { url }
}
<RichTextEditor
  v-model="content"
  :extensions="[ImageUploadExtension.configure({ upload })]"
  :toolbar-items="[...defaultToolbarItems, imageUploadToolbarItem]"
/>

Text lightbox

Wraps the selected text in <a data-lightbox="true" data-lightbox-type="text" …>.

import { LightboxExtension, lightboxToolbarItem } from '@tiaohsun/vue-rich-text-editor/extensions/lightbox'
<RichTextEditor
  v-model="content"
  :extensions="[LightboxExtension.configure({})]"
  :toolbar-items="[...defaultToolbarItems, lightboxToolbarItem]"
/>

Image lightbox

Adds a lightbox attribute to images. When toggled on, the image renders with data-lightbox="true" / data-lightbox-src / data-lightbox-alt for a front-end lightbox library to pick up.

This extension only produces the markup contract — it does not display a lightbox itself. It is opt-in: without it, the image bubble-menu lightbox toggle and the in-editor 🔍 badge do not appear. Load it only when your front-end actually wires up a lightbox viewer.

import { ImageLightbox } from '@tiaohsun/vue-rich-text-editor/extensions/image-lightbox'
<RichTextEditor v-model="content" :extensions="[ImageLightbox]" />

Layout blocks (grid, flex-columns)

Two opt-in multi-column layout blocks. Multi-column layout is not a core feature of CKEditor 5 or TinyMCE either, so these ship as optional extensions rather than defaults.

  • grid — a responsive grid with separate desktop (1–6) and mobile (1–3) column counts, adjustable from the block's own toolbar.
  • flex-columns — two columns where either side takes the remaining width, and which stacks vertically in narrow containers.
import { GridExtension, createGridToolbarItem } from '@tiaohsun/vue-rich-text-editor/extensions/grid'
import { FlexColumnsExtension, createFlexColumnsToolbarItem } from '@tiaohsun/vue-rich-text-editor/extensions/flex-columns'
<RichTextEditor
  v-model="content"
  :extensions="[GridExtension, FlexColumnsExtension]"
  :toolbar-items="[...defaultToolbarItems, createGridToolbarItem(), createFlexColumnsToolbarItem()]" />

Both commands are also available directly: editor.commands.insertGrid({ minColumns, maxColumns }) and editor.commands.insertFlexColumns({ growSide }).

Exported HTML is self-contained — container and item styles are written inline, so rendered content needs no stylesheet. The editor's own styling for these blocks lives in /styles like everything else.


Toolbar

The toolbar is a flat list of items. Compose your own from the defaults:

import { defaultToolbarItems, exportHtmlToolbarItem } from '@tiaohsun/vue-rich-text-editor'
import type { ToolbarItem } from '@tiaohsun/vue-rich-text-editor'

const toolbarItems: ToolbarItem[] = [
  ...defaultToolbarItems,
  { type: 'separator' },
  exportHtmlToolbarItem,
  myCustomButton,
]

A ToolbarItem is one of:

  • Button — { name, icon, label, command(editor), isActive?, isDisabled? }
  • Dropdown — { type: 'dropdown', name, options, getValue(editor), command(editor, value) }
  • Separator — { type: 'separator' }

Built-in SVG icons are exported (e.g. BoldIcon, ImageIcon, …) for consistent styling.


Output format

  • HTML (output-format="html", default) — convenient, but the exported markup may include presentational classes; best for same-stack rendering.
  • Tiptap JSON (output-format="json") — the recommended storage format. Lossless, stable, and not coupled to HTML serialization.

License

MIT — see LICENSE.

All editor capabilities are provided by Tiptap (MIT) and remain under Tiptap's licensing; Tiptap is a peer dependency, not bundled. Built-in SVG icon paths are derived from Lucide (ISC). See THIRD_PARTY_NOTICES.md for third-party license notices.

Related Packages

@shamimsofte/universal-editor-vue2

Vue 2 adapter for Universal Rich Text Editor (Vue 2.6+ & 2.7)

571 0
@shamimsofte/universal-editor-vue3

Vue 3 adapter and components for Universal Editor

564 0
@rozie-ui/tiptap-vue

Idiomatic Vue rich-text editor — one Rozie source compiled to Vue wrapping TipTa...

2,146 1
guodu-tiptap

A WYSIWYG rich-text editor using tiptap

842 18
@markweave/vue2

Vue 2 adapter for the Markweave Markdown-first WYSIWYG editor.

9,500 1

Version History

Version Released Vue License
0.5.0 ^3.5.0 MIT
0.4.0 ^3.5.0 MIT
0.3.0 ^3.5.0 MIT
0.2.1 ^3.5.0 MIT
0.2.0 ^3.5.0 MIT