TextEditor

A rich text editor based on TipTap for creating and formatting content. Offers intuitive controls, styling options, and smooth editing experience.

Default

vue
<script setup lang="ts">
import { ref } from 'vue'
import { TextEditor } from 'frappe-ui'

const value = ref(`
<div>
  <h2>Heading 2</h2>
  <p>
    This is a paragraph with <strong>bold</strong> and <em>italic</em> text.
  </p>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
  <p>
    <a href="https://frappe.io">Frappe</a>
  </p>
  <pre><code class="language-javascript">import { Button } from 'frappe-ui'
const value = ref(true);</code></pre>
</div>
`)
</script>

<template>
  <TextEditor
    editor-class="prose-sm min-h-[4rem] border rounded-b-lg border-t-0 p-2"
    :content="value"
    placeholder="Type something..."
    @change="(val) => (value.value = val)"
    :bubbleMenu="true"
    :fixed-menu="true"
  />
</template>

Comment Editor

vue
<script setup lang="ts">
import { ref } from 'vue'
import { EditorContent } from '@tiptap/vue-3'
import { Button, TextEditor, TextEditorFixedMenu } from 'frappe-ui'

const customValue = ref('')
const customButtons = [
  'Paragraph',
  ['Heading 2', 'Heading 3', 'Heading 4'],
  'Separator',
  'Bold',
  'Italic',
  'Separator',
  'Bullet List',
  'Numbered List',
  'Separator',
  'Link',
  'Image',
]
</script>

<template>
  <TextEditor
    ref="textEditor"
    editor-class="prose-sm max-w-none min-h-[4rem]"
    :content="customValue"
    @change="(val) => (customValue.value = val)"
    :starterkit-options="{ heading: { levels: [2, 3, 4] } }"
    placeholder="Write something amazing..."
  >
    <template v-slot:editor="{ editor }">
      <EditorContent
        class="max-h-[50vh] overflow-y-auto border rounded-lg p-4"
        :editor="editor"
      />
    </template>

    <template v-slot:bottom>
      <div
        class="mt-2 flex flex-col justify-between sm:flex-row sm:items-center"
      >
        <TextEditorFixedMenu
          class="-ml-1 overflow-x-auto"
          :buttons="customButtons"
        />
        <div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
          <Button>Cancel</Button>
          <Button variant="solid">Submit</Button>
        </div>
      </div>
    </template>
  </TextEditor>
</template>

API Reference

Show types
typescript
import { type Component } from 'vue'
import { type UploadedFile } from '../../utils/useFileUpload'
import { type MentionSuggestionItem } from './extensions/mention/mention-extension'

type ConfigureMentionOptions =
  | {
      mentions: MentionSuggestionItem[]
      component?: Component
    }
  | MentionSuggestionItem[]
  | null

export interface TextEditorProps {
  /** Initial editor content (HTML/string). `null` renders an empty editor */
  content?: string | null

  /** Placeholder text or dynamic placeholder resolver */
  placeholder?: string | (() => string)

  /** Custom classes applied to the editor root */
  editorClass?: string | string[] | object

  /** Toggles editability of the editor */
  editable?: boolean

  /** Autofocus the editor on mount */
  autofocus?: boolean

  /** Enables bubble menu or provides custom bubble menu items */
  bubbleMenu?: boolean | any[]

  /** Configuration options for the bubble menu */
  bubbleMenuOptions?: object

  /** Enables fixed menu or provides custom fixed menu items */
  fixedMenu?: boolean | any[]

  /** Enables floating menu or provides custom floating menu items */
  floatingMenu?: boolean | any[]

  /** Custom TipTap extensions */
  extensions?: any[]

  /** Options passed to TipTap StarterKit */
  starterkitOptions?: any

  /** Mention extension configuration */
  mentions?: ConfigureMentionOptions

  /** Tag / hashtag configuration */
  tags?: any[] | null

  /** Async file upload handler (used for images, files, etc.) */
  uploadFunction?: (file: File) => Promise<UploadedFile>

  /** Extra arguments passed to the upload function */
  uploadArgs?: object
}

export interface TextEditorEmits {
  /** Fired whenever editor content changes */
  change: [content: string]

  /** Fired when the editor gains focus */
  focus: [event: FocusEvent]

  /** Fired when the editor loses focus */
  blur: [event: FocusEvent]

  /** Fired on every editor transaction */
  transaction: [editor: object]
}
content
= null
string | null

Initial editor content (HTML/string). `null` renders an empty editor

placeholder
= ""
string | (() => string)

Placeholder text or dynamic placeholder resolver

editorClass
= "prose-sm"
string | object | string[]

Custom classes applied to the editor root

editable
= true
boolean

Toggles editability of the editor

autofocus
= false
boolean

Autofocus the editor on mount

bubbleMenu
= false
boolean | any[]

Enables bubble menu or provides custom bubble menu items

bubbleMenuOptions
= {}
object

Configuration options for the bubble menu

fixedMenu
= false
boolean | any[]

Enables fixed menu or provides custom fixed menu items

floatingMenu
= false
boolean | any[]

Enables floating menu or provides custom floating menu items

extensions
= []
any[]

Custom TipTap extensions

starterkitOptions
= {}
any

Options passed to TipTap StarterKit

mentions
= null
ConfigureMentionOptions

Mention extension configuration

tags
= null
any[] | null

Tag / hashtag configuration

uploadFunction
((file: File) => Promise<UploadedFile>)

Async file upload handler (used for images, files, etc.)

uploadArgs
object

Extra arguments passed to the upload function

top
{ editor: { contentComponent: { uid: number; type: FunctionalComponent<{}, {}, any, {}> | { [x: stri
editor
{ editor: { contentComponent: { uid: number; type: FunctionalComponent<{}, {}, any, {}> | { [x: stri
bottom
{ editor: { contentComponent: { uid: number; type: FunctionalComponent<{}, {}, any, {}> | { [x: stri
change
[content: string]

Fired after the value is committed.

focus
[event: FocusEvent]
blur
[event: FocusEvent]
transaction
[editor: object]