Quick start: plain Vue
SimpleEditor is the two core features alone (core-content and core-ui): bold/italic/underline/strike/code/link, headings, lists, quotes, code blocks, horizontal rule, undo/redo. No backend requests, no Yjs, no feature code. The runnable reference is examples/vue-simple; start it with bun run --cwd examples/vue-simple dev (port 3040). Its sample picker loads five demo documents — „Start“, „Formatierung“ (marks, lists, quote, code block, rule), „Gliederung“ (three outline levels), „Hinweise“ (the own „Hinweis“ block) and „Ungültig (Block-JSON)“ (advanced JSON, rejected through contentError) — beside the toolbar and outline, with the JSON and Markdown of the document next to the editor. The /hinweis entry and the callout behind it are an own block added through the extensions and commands props; the tutorial shows how, and how to go further with a full feature.
Setup
Starting a new app from nothing? The files below plus these three are all that examples/vue-simple has:
package.jsondependencies:@skaile/forge-editor(workspace:*inside this repository),vue,@nuxt/ui,tailwindcss,@iconify-json/lucide,@tiptap/core,@tiptap/pm,@tiptap/vue-3,yjs,@hocuspocus/provider; dev dependenciesvite,@vitejs/plugin-vue,typescript,vue-tsc; scriptsdev(vite),build(vite build),typecheck(vue-tsc --noEmit -p tsconfig.json). Use the exact version ranges of the example'spackage.json.index.htmlwith<div id="app"></div>and<script type="module" src="/src/main.ts"></script>.tsconfig.jsonwithmoduleResolution: "Bundler",types: ["vite/client"],strict,verbatimModuleSyntaxandinclude: ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"](see the example'stsconfig.json).
Optional: Nuxt UI resolves i-lucide-* icons at build time, but a workspace library lies outside the Vite root it scans, so without the extra config the page fetches the toolbar's icons from the Iconify API at runtime. The example's vite.config.ts collects the library's icon names into ui({ icon: { clientBundle: { scan: true, icons } } }) to bundle them.
vite.config.ts — Nuxt UI's Vite plugin without a router:
import ui from '@nuxt/ui/vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({ plugins: [vue(), ui({ router: false })] })
main.ts — Nuxt UI and the editor context. createEditor() (from the Vue layer, @skaile/forge-editor/vue) owns the settings of this app and is required by every editor component:
import ui from '@nuxt/ui/vue-plugin'
import { createEditor } from '@skaile/forge-editor/vue'
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).use(ui).use(createEditor()).mount('#app')
createEditor() loads and saves the settings per account through its backend: with the default backend (createFetchBackend, the HTTP routes of the Nuxt module over the platform fetch) the first useEditorSettings() asks GET /api/editor/settings and every change is sent with PUT. They are local-only (localStorage, tiptap-editor:settings) when the backend offers no settings capability: createEditor({ backend: createMemoryBackend() }). createEditor({ backend }) swaps the whole data layer of all features (documents, uploads, mention lookups, settings, themes, AI); createHttpBackend({ fetch }) (@skaile/forge-editor/adapters/http) lets a host hand in its own HTTP client such as Nuxt's $fetch. createEditor({ locale: 'en' }) switches every string the editor renders to English (default 'de'), createEditor({ messages: { 'toolbar.bold': 'Strong' } }) overrides single messages; see Language. Components read useEditorI18n() ({ t, locale }); the Nuxt module takes the same two options as tiptapEditor: { locale, messages }.
createEditor() is a thin Vue plugin. The headless core (@skaile/forge-editor/core) imports no Vue: createEditorContext(options) returns the framework-free context with the same options, whose settings is a store (get(), set(value), update(recipe), subscribe(listener)) and whose ready and synced are read-only stores. createEditor() creates that context, provides it to the app and disposes it on unmount; Vue components read the settings through useEditorSettings() (reactive refs) from @skaile/forge-editor/vue.
style.css — Tailwind, Nuxt UI, then the core CSS, and let Tailwind scan the library's whole dist (the Features' features/*/components sit beside vue; in this repository the examples scan the sibling's src instead):
@import "tailwindcss";
@import "@nuxt/ui";
@import "@skaile/forge-editor/styles/core.css";
@source "../node_modules/@skaile/forge-editor/dist"; /* relative path from this CSS file to the installed package's dist */
App.vue wraps everything in <UApp> (overlays, tooltips, toasts).
Mount the editor
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import type { Editor, JSONContent } from '@tiptap/core'
import { SimpleEditor } from '@skaile/forge-editor/vue/simple'
import { EditorToolbar } from '@skaile/forge-editor/vue'
import { EditorOutline } from '@skaile/forge-editor/outline/vue'
const editor = shallowRef<Editor>()
const content = ref<JSONContent>({ type: 'doc', content: [{ type: 'paragraph' }] })
</script>
<template>
<UApp>
<EditorToolbar :editor="editor" />
<SimpleEditor v-model="content" @ready="editor = $event" />
<EditorOutline :editor="editor" />
</UApp>
</template>
Try it here: type, use the toolbar, switch to read-only and watch the JSON v-model and the outline.
Next steps
The tutorial continues from this editor; the props and events of SimpleEditor are covered there. To add features such as mentions, media, tables and block UI, see Compose features.