Skip to content

useSaEditor ​

A Tiptap editor with the template's extension set already configured — headings, lists, links, images, tables and highlighted code blocks.

Open in demo

Import ​

ts
import { useSaEditor } from '@core/editor/useEditor'

The file is useEditor.ts, but the export is useSaEditor — Tiptap's own useEditor is what it wraps.

Signature ​

ts
function useSaEditor(options?: SaEditorOptions): ShallowRef<Editor | undefined>

interface SaEditorOptions {
  modelValue?: string
  placeholder?: string
  readonly?: boolean
  onUpdate?: (html: string) => void
}

Parameters ​

ParameterTypeDefaultDescription
modelValuestring''Starting HTML.
placeholderstringcommon.startTypingPlaceholder text, and the region's accessible name.
readonlybooleanfalseRenders the content without letting it be edited.
onUpdate(html: string) => void—Called on every change with editor.getHTML().

Returns ​

A ShallowRef<Editor | undefined>. It is undefined until the editor mounts, so guard it or use v-if.

The extensions ​

ExtensionWhat it adds
StarterKitHeadings, paragraphs, bold, italic, lists, quotes, history, and Link with openOnClick: false. Its own codeBlock is switched off.
ImageInline images.
PlaceholderThe empty-state text.
Table, TableRow, TableHeader, TableCellResizable tables.
CodeBlockLowlightCode blocks highlighted by lowlight's common language set.

Example ​

vue
<script setup lang="ts">
import { useSaEditor } from '@core/editor/useEditor'
import { EditorContent } from '@tiptap/vue-3'
import { ref } from 'vue'

const html = ref('<p>Start with a sentence.</p>')

const editor = useSaEditor({
  modelValue: html.value,
  placeholder: 'Write the release note',
  onUpdate: value => (html.value = value),
})
</script>

<template>
  <div class="space-y-3">
    <div v-if="editor" class="flex gap-2">
      <button type="button" @click="editor.chain().focus().toggleBold().run()">
        Bold
      </button>
      <button type="button" @click="editor.chain().focus().toggleBulletList().run()">
        List
      </button>
    </div>

    <EditorContent :editor="editor" class="rounded-lg border p-3" />
  </div>
</template>

Notes ​

  • Link, Underline and a plain code block already ship inside StarterKit in Tiptap v3. Adding @tiptap/extension-link again triggers the duplicate-extension warning, so Link is configured through StarterKit and the built-in codeBlock is disabled in favor of CodeBlockLowlight.
  • The lowlight registry is created once at module scope and shared by every editor on the page.
  • The editable region gets an aria-label. Tiptap sets role="textbox" and aria-multiline, but a contenteditable div is not a form element, so no <label for> can name it. The placeholder, or the default common.richTextEditor, fills that gap.
  • readonly maps to Tiptap's editable: false. The content still renders.
  • The default placeholder and label are translation keys, so they follow the language switcher.
  • SaEditor.vue wraps this with a toolbar. Reach for the composable when you want a different toolbar, not a different editor.
  • Forms — the editor field type.
  • Schema — where a field asks for one.