Component
Document editor
Document editorA client-only WYSIWYG editing surface with structured JSON output, app-owned AI rewrite suggestions, media insertion, video embeds, and optional Yjs collaboration.
Demo
AI-ready editing workspace
A Pinia setup store loads and saves structured document JSON, emulates an AI rewrite endpoint, and opens DomMediaBrowser when the editor image tool requests media.
Untitled document
Draft copy for the July product launch.
Form
Textarea-style v-model
A compact form field setup uses DomWysiwyg with a normal v-model binding, a reduced WYSIWYG toolbar, and no collaboration, rewrite, media, or external store wiring.
Extend
Toolbar tools and document behavior
Custom toolbar tools
Pass customTools records. Each command receives the editor instance, current selection, and helpers such as requestRewrite.
Typed editor behavior
Use extensions or configureExtensions when a feature belongs inside the document model, such as mentions, embeds, or custom marks.
App-owned popups
Menus for people, assets, records, and commands stay in the app. The editor supplies the trigger context; the owner renders and commits the selected item.
Register a custom toolbar tool, open an app-owned user picker, and insert a mention into the active editor.
Custom tools
App-owned Toolbar tools are plain records. A command gets the editor instance and helper methods; the owner can still listen to tool-action.
Last tool: None yet
Boundary
Editor experience, not document output
Client-only runtime
DomDocumentEditor dynamically imports the Tiptap runtime after mount. The docs shell can SSR, but the editing canvas belongs to the browser.
Structured persistence
The editor emits Tiptap JSON, HTML, and plain text in the change payload. Store JSON for editing and render public document output through a separate renderer.
App-owned integrations
AI rewrite and media picking are events. Apps keep model calls, media permissions, and storage rules behind their own API boundaries.
Usage
Client editor integration
The component is safe to place on an SSR page because the editor packages are loaded from onMounted. Use the page for an authenticated editing workflow and keep SEO rendering separate.
AI
Suggestion contract
When the user asks for a rewrite, the component emits the selected text, JSON slice, document range, and a respond callback. The owner can call an AI endpoint, then pass back a suggestion.
Pending suggestions mark the original range inline and render a word-level diff in the review rail. Accepting replaces the current marked range; rejecting removes the mark.
Collaboration
Yjs adapter
Pass a Yjs document or fragment when the app wants true multi-user editing. The component wires Tiptap's collaboration extension and, when a provider is present, collaboration carets.
The AI can be represented as a collaborator in the header today and as a provider awareness user later while it is actively proposing changes.
Demo API
Store and API boundaries
Reference
Component API
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
modelValue | object | string | Record<string, unknown> | () => … | Controlled editor content. JSON is preferred; HTML strings are accepted for simple imports. |
placeholder | string | string | 'Write something useful...' | Placeholder shown inside empty blocks. |
chrome | 'document' | 'field' | 'none' | false | string | 'document' | Render the full document workspace, form-field chrome, or no outer field chrome. |
output | 'json' | 'html' | string | 'json' | Value emitted through update:modelValue. |
editable | boolean | boolean | true | Allow editing and toolbar commands. |
title | string | string | 'Document editor' | Accessible editor title. |
status | string | string | '' | Optional save or collaboration status label. |
tools | array | Array<unknown> | () => … | Built-in toolbar tool ids to show. |
customTools | array | Array<unknown> | [] | Additional toolbar actions emitted through tool-action. |
suggestions | array | Array<unknown> | [] | Externally supplied rewrite suggestions. |
collaborators | array | Array<unknown> | [] | Human, AI, or system collaborators shown in the editor header. |
extensions | array | Array<unknown> | [] | Additional Tiptap extensions appended to the built-in editor extension list. |
configureExtensions | function | Function | — | Optional hook that receives the default extension list and Tiptap runtime modules, then returns the final extension list. |
collaboration | object | Record<string, unknown> | null | Optional Yjs collaboration adapter with document/fragment, provider, field, and user. |
rewriteProvider | function | Function | — | Optional async function that returns a rewrite suggestion for the current selection. |
aiUser | object | Record<string, unknown> | () => … | AI author metadata used for locally created rewrite suggestions. |
showSuggestionRail | boolean | boolean | true | Show the review rail for pending AI suggestions. |
showToolbar | boolean | boolean | true | Show the WYSIWYG toolbar. Slash commands remain available when enabled. |
showSlashMenu | boolean | boolean | true | Open the block command menu when the user types / at the start of a block. |
autofocus | boolean | boolean | false | Focus the editor when the client runtime mounts. |
minHeight | string | string | '34rem' | Minimum height for the editable document canvas. |
Field props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
id | string | string | '' | Optional ID override. By default parent forms derive the input ID from the field path using underscores. |
name | string | string | '' | Local field name. Parent forms derive the full field path and native HTML name from the form hierarchy. |
label | string | string | '' | Visible field label. |
description | string | string | '' | Optional helper copy below the field. |
required | boolean | boolean | false | Mark the field as required. |
disabled | boolean | boolean | false | Disable field interaction. |
readOnly | boolean | boolean | false | Show the value but prevent editing. |
invalid | boolean | boolean | false | Mark the field invalid. |
errors | array | object | string | Array< | [] | Validation errors for this field. |
visible | boolean | boolean | true | Show or hide the field. |
validators | array | Array<unknown> | [] | Validators attached to this field. Use functions in Vue code, or serializable records such as { name: "minLength", props: { min: 2 } } in generated schemas. |
validateOnBlur | boolean | boolean | true | Run validators when the field loses focus. |
Auto-generated from Document editor.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @update:modelValue | Record<string, unknown> | string | Fired with JSON or HTML content depending on the output prop. |
| @change | DocumentEditorChangePayload | Fired with JSON, HTML, and plain text whenever the editor changes. |
| @selection-change | DocumentEditorSelectionPayload | Fired when the editor selection changes. |
| @rewrite-request | DocumentEditorRewriteRequest | Fired when the user asks AI to rewrite the selected range. |
| @suggestion-create | DocumentEditorSuggestion | Fired after a rewrite suggestion is added to the review rail. |
| @suggestion-accept | DocumentEditorSuggestion | Fired after a suggestion is accepted. |
| @suggestion-reject | DocumentEditorSuggestion | Fired after a suggestion is rejected. |
| @media-request | ({ editor }) | Fired when the image tool should open an app-owned media picker. |
| @media-insert | DocumentEditorMediaPayload | Fired after insertImage inserts a media-backed image node. |
| @video-insert | DocumentEditorVideoPayload | Fired after a video embed node is inserted. |
| @tool-action | ({ tool, editor }) | Fired when a custom toolbar tool is activated. |
| @ready | ({ editor }) | Fired after the browser-only editor runtime mounts. |
| @runtime-error | Error | Fired when the editor runtime cannot mount. |
| @focus | FocusEvent | Fired when the editable document receives focus. |
| @blur | FocusEvent | Fired when the editable document loses focus. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #toolbar-start | { editor, active } | Add controls before the built-in toolbar groups. |
| #toolbar-end | { editor, active } | Add controls after the built-in toolbar groups. |
| #suggestion | { suggestion, accept, reject } | Replace a suggestion review card. |
| #empty-suggestions | — | Replace the empty suggestion rail state. |
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
modelValue | object | string | Record<string, unknown> | () => … | Controlled editor content. JSON is preferred; HTML strings are accepted for simple imports. |
output | 'json' | 'html' | string | 'json' | Value emitted through update:modelValue. |
editable | boolean | boolean | true | Allow editing and toolbar commands. |
title | string | string | '' | Accessible editor title. Defaults to the field label. |
status | string | string | '' | Optional save or validation status label. |
tools | array | Array<unknown> | () => … | Built-in toolbar tool ids to show. |
customTools | array | Array<unknown> | [] | Additional toolbar actions emitted through tool-action. |
showToolbar | boolean | boolean | true | Show the WYSIWYG toolbar. |
showSlashMenu | boolean | boolean | true | Open the block command menu when the user types / at the start of a block. |
minHeight | string | string | '16rem' | Minimum height for the editable field. |
Field props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
id | string | string | '' | Optional ID override. By default parent forms derive the input ID from the field path using underscores. |
name | string | string | '' | Local field name. Parent forms derive the full field path and native HTML name from the form hierarchy. |
label | string | string | '' | Visible field label. |
description | string | string | '' | Optional helper copy below the field. |
placeholder | string | string | '' | Placeholder shown when the control is empty. |
required | boolean | boolean | false | Mark the field as required. |
disabled | boolean | boolean | false | Disable field interaction. |
readOnly | boolean | boolean | false | Show the value but prevent editing. |
invalid | boolean | boolean | false | Mark the field invalid. |
errors | array | object | string | Array< | [] | Validation errors for this field. |
visible | boolean | boolean | true | Show or hide the field. |
validators | array | Array<unknown> | [] | Validators attached to this field. Use functions in Vue code, or serializable records such as { name: "minLength", props: { min: 2 } } in generated schemas. |
validateOnBlur | boolean | boolean | true | Run validators when the field loses focus. |
chrome | 'field' | 'none' | false | string | 'field' | Render default field chrome, or hide chrome while keeping form state wiring. |
Auto-generated from WYSIWYG field.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @update:modelValue | Record<string, unknown> | string | Fired with JSON or HTML content depending on the output prop. |
| @change | DocumentEditorChangePayload | Fired with JSON, HTML, and plain text whenever the editor changes. |
| @selection-change | DocumentEditorSelectionPayload | Fired when the editor selection changes. |
| @tool-action | ({ tool, editor }) | Fired when a custom toolbar tool is activated. |
| @ready | ({ editor }) | Fired after the browser-only editor runtime mounts. |
| @runtime-error | Error | Fired when the editor runtime cannot mount. |
| @focus | FocusEvent | Fired when the editable document receives focus. |
| @blur | FocusEvent | Fired when the editable document loses focus. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.