Vue Rich Text Editor
Scribe Editor integrates seamlessly with Vue 3's Composition API. Under 50KB, zero dependencies, v-model support, floating toolbar, and a clean useScribe composable. Works with Nuxt 3 and Vite out of the box.
Built for the Vue 3 Composition API
Composition API native
Built for Vue 3 <script setup> with onMounted / onBeforeUnmount lifecycle. A clean composable is all you need.
v-model compatible
Emit update:modelValue from onChange. Bind with v-model="content" and it just works — no wrappers.
Works with Nuxt 3
Wrap in <ClientOnly> to skip SSR. Scribe hydrates cleanly with no hydration mismatches or window errors.
Zero dependencies
Under 50KB gzipped, no ProseMirror, no Quill. Your Vue bundle stays as small as possible.
Vue 3 Component with v-model
A complete, reusable Vue SFC with defineExpose for imperative access and v-model for reactive binding.
<!-- ScribeEditor.vue -->
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Scribe, type EditorInstance } from 'scribejs-editor'
import 'scribejs-editor/dist/scribe.css'
const props = defineProps<{ modelValue?: string }>()
const emit = defineEmits<{ 'update:modelValue': [string] }>()
const editorEl = ref<HTMLElement | null>(null)
let editor: EditorInstance | null = null
onMounted(() => {
if (!editorEl.value) return
editor = Scribe.init(editorEl.value, {
placeholder: 'Start writing...',
onChange: (html) => emit('update:modelValue', html),
})
})
onBeforeUnmount(() => editor?.destroy())
// Expose API to parent via template ref
defineExpose({
bold: () => editor?.bold(),
italic: () => editor?.italic(),
getHTML: () => editor?.getHTML() ?? '',
})
</script>
<template>
<div ref="editorEl" v-html="modelValue" />
</template>Composable Pattern
Extract editor logic into a reusable useScribe composable and share it across components.
// composables/useScribe.ts
import { ref, onMounted, onBeforeUnmount, type Ref } from 'vue'
import { Scribe, type EditorInstance } from 'scribejs-editor'
export function useScribe(targetRef: Ref<HTMLElement | null>) {
const editor = ref<EditorInstance | null>(null)
onMounted(() => {
if (!targetRef.value) return
editor.value = Scribe.init(targetRef.value, {
toolbar: 'floating',
})
})
onBeforeUnmount(() => editor.value?.destroy())
return {
bold: () => editor.value?.bold(),
italic: () => editor.value?.italic(),
heading: (n: 1|2|3|4|5|6) => editor.value?.heading(n),
link: (url: string) => editor.value?.link(url),
getHTML: () => editor.value?.getHTML() ?? '',
}
}
// Usage in any component:
// const editorEl = ref(null)
// const { bold, italic, getHTML } = useScribe(editorEl)Nuxt 3 Integration
Use Nuxt's built-in <ClientOnly> to skip SSR for DOM-bound editors.
<!-- Nuxt 3 — wrap in <ClientOnly> to skip SSR -->
<template>
<ClientOnly>
<ScribeEditor v-model="content" />
</ClientOnly>
</template>
<script setup>
const content = ref('<p>Hello Nuxt!</p>')
</script>Full feature set — ready to use
Ship a Vue rich text editor today.
One npm install. One composable. A complete WYSIWYG editor in your Vue 3 app in minutes.
Vue 3 + Scribe — common questions
What is the best rich text editor for Vue 3?
Scribe Editor is one of the best lightweight WYSIWYG editors for Vue 3. It's under 50KB with zero dependencies, integrates via Composition API (onMounted / onBeforeUnmount), supports v-model with update:modelValue, and includes a floating toolbar. It works with both Vite and Nuxt 3.
How do I add a rich text editor to a Vue 3 app?
Install scribejs-editor, create a Vue component with a ref to the editor element, call Scribe.init(el, options) in onMounted, and clean up with editor.destroy() in onBeforeUnmount. Emit update:modelValue in the onChange callback for v-model support.
Does Scribe Editor work with Nuxt 3?
Yes. Wrap your Scribe-powered component in Nuxt's <ClientOnly> tag to prevent server-side rendering. The editor will initialize on the client after hydration with no window or document errors.
Is Scribe a good alternative to Tiptap for Vue?
Yes, especially if you don't need Tiptap's ProseMirror-based custom schemas. Scribe is 3x smaller (50KB vs 150KB+), doesn't require you to configure extensions, and has first-class Vue 3 support — unlike Tiptap which is primarily React-focused. Scribe's Vue composable pattern fits naturally with the Composition API.
Also compare Scribe with:
Use Scribe in your framework: