@tiaohsun/vue-rich-text-editor
@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
Detailsextension (native<details>) - Placeholder, and
Ctrl/Cmd+Shift+Enterto 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: clipandoverflow: auto/scrollare 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
Vue 2 adapter for Universal Rich Text Editor (Vue 2.6+ & 2.7)
Idiomatic Vue rich-text editor — one Rozie source compiled to Vue wrapping TipTa...
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 | |
| 0.1.1 | ^3.5.0 | MIT | |
| 0.1.0 | ^3.5.0 | MIT |