Skip to main content
Tiptap can be used directly with vanilla JavaScript without any framework. This is useful for projects that don’t use a framework or when you want full control over the integration.

Installation

1

Install the core package

Install the core package and any extensions you need:
npm install @tiptap/core @tiptap/starter-kit
2

Import and use

Import the Editor class and extensions in your JavaScript:
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

Core Concepts

Editor Class

The Editor class is the core of Tiptap. It manages the editor state, extensions, and view.

Type Signature

class Editor {
  constructor(options: EditorOptions)
  
  // Properties
  view: EditorView
  state: EditorState
  schema: Schema
  isDestroyed: boolean
  isFocused: boolean
  
  // Methods
  destroy(): void
  setOptions(options: Partial<EditorOptions>): void
  getHTML(): string
  getJSON(): JSONContent
  getText(): string
  setContent(content: Content): void
  commands: Commands
  chain(): ChainedCommands
  can(): CanCommands
  isActive(name: string, attributes?: {}): boolean
  // ... and many more
}

interface EditorOptions {
  element: Element
  extensions: Extension[]
  content?: Content
  editable?: boolean
  autofocus?: boolean | 'start' | 'end' | number
  injectCSS?: boolean
  onCreate?: (props: { editor: Editor }) => void
  onUpdate?: (props: { editor: Editor, transaction: Transaction }) => void
  onSelectionUpdate?: (props: { editor: Editor, transaction: Transaction }) => void
  onTransaction?: (props: { editor: Editor, transaction: Transaction }) => void
  onFocus?: (props: { editor: Editor, event: FocusEvent }) => void
  onBlur?: (props: { editor: Editor, event: FocusEvent }) => void
  onDestroy?: () => void
  // ... and more
}

Basic Usage

Here’s a minimal example of creating an editor:
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

// Create the editor
const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

HTML Structure

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Tiptap Editor</title>
  <style>
    /* Basic editor styles */
    .tiptap {
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 4px;
      min-height: 200px;
    }
    
    .tiptap:focus {
      outline: 2px solid #0066cc;
      outline-offset: 2px;
    }
  </style>
</head>
<body>
  <div id="editor"></div>
  <script type="module" src="/main.js"></script>
</body>
</html>

Complete Working Examples

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

// Create editor
const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: `
    <h2>Hi there,</h2>
    <p>this is a <em>basic</em> example of <strong>Tiptap</strong>.</p>
  `,
})

Working with Content

Getting Content

// Get HTML
const html = editor.getHTML()
console.log(html)
// Output: '<p>Hello <strong>World</strong>!</p>'

// Get JSON
const json = editor.getJSON()
console.log(json)
// Output: { type: 'doc', content: [...] }

// Get plain text
const text = editor.getText()
console.log(text)
// Output: 'Hello World!'

// Get text with custom separator
const textWithBreaks = editor.getText({ blockSeparator: '\n\n' })

Setting Content

// Set HTML content
editor.commands.setContent('<p>New <strong>content</strong>!</p>')

// Set JSON content
editor.commands.setContent({
  type: 'doc',
  content: [
    {
      type: 'paragraph',
      content: [
        { type: 'text', text: 'New content!' },
      ],
    },
  ],
})

// Clear content
editor.commands.clearContent()

// Insert content at current position
editor.commands.insertContent('<p>Inserted content</p>')

Commands

Tiptap provides a powerful command system:
// Single command
editor.commands.toggleBold()

// Chain multiple commands
editor
  .chain()
  .focus()
  .toggleBold()
  .toggleItalic()
  .run()

// Check if a command can be executed
if (editor.can().toggleBold()) {
  console.log('Can toggle bold')
}

// Common commands
editor.commands.setHeading({ level: 1 })
editor.commands.toggleBulletList()
editor.commands.toggleOrderedList()
editor.commands.setHorizontalRule()
editor.commands.undo()
editor.commands.redo()

Check Active States

// Check if mark/node is active
if (editor.isActive('bold')) {
  console.log('Bold is active')
}

if (editor.isActive('heading', { level: 1 })) {
  console.log('H1 is active')
}

if (editor.isActive('link')) {
  console.log('Link is active')
}

Event Handling

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

// Listen to events
editor.on('update', ({ editor }) => {
  console.log('Content updated:', editor.getHTML())
})

editor.on('selectionUpdate', ({ editor }) => {
  console.log('Selection changed')
})

editor.on('focus', ({ editor, event }) => {
  console.log('Editor focused')
})

editor.on('blur', ({ editor, event }) => {
  console.log('Editor blurred')
})

// Remove event listener
const handler = ({ editor }) => console.log('Updated')
editor.on('update', handler)
editor.off('update', handler)

Toolbar Example

Here’s a complete example with a toolbar:
<!DOCTYPE html>
<html>
<head>
  <style>
    .toolbar {
      display: flex;
      gap: 0.5rem;
      margin-bottom: 1rem;
      padding: 0.5rem;
      border: 1px solid #ddd;
      border-radius: 4px;
      background: #f9f9f9;
    }
    
    .toolbar button {
      padding: 0.5rem 1rem;
      border: 1px solid #ddd;
      border-radius: 4px;
      background: white;
      cursor: pointer;
    }
    
    .toolbar button:hover {
      background: #f0f0f0;
    }
    
    .toolbar button.is-active {
      background: #333;
      color: white;
    }
    
    .tiptap {
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 4px;
      min-height: 200px;
    }
  </style>
</head>
<body>
  <div id="toolbar" class="toolbar"></div>
  <div id="editor"></div>
  
  <script type="module">
    import { Editor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'
    
    const editor = new Editor({
      element: document.querySelector('#editor'),
      extensions: [StarterKit],
      content: '<p>Hello World!</p>',
    })
    
    // Helper to create toolbar buttons
    function createButton(label, command, checkActive) {
      const button = document.createElement('button')
      button.textContent = label
      button.addEventListener('click', command)
      
      if (checkActive) {
        editor.on('selectionUpdate', () => {
          if (checkActive()) {
            button.classList.add('is-active')
          } else {
            button.classList.remove('is-active')
          }
        })
      }
      
      return button
    }
    
    const toolbar = document.querySelector('#toolbar')
    
    // Add buttons
    toolbar.appendChild(createButton(
      'Bold',
      () => editor.chain().focus().toggleBold().run(),
      () => editor.isActive('bold')
    ))
    
    toolbar.appendChild(createButton(
      'Italic',
      () => editor.chain().focus().toggleItalic().run(),
      () => editor.isActive('italic')
    ))
    
    toolbar.appendChild(createButton(
      'H1',
      () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
      () => editor.isActive('heading', { level: 1 })
    ))
    
    toolbar.appendChild(createButton(
      'Bullet List',
      () => editor.chain().focus().toggleBulletList().run(),
      () => editor.isActive('bulletList')
    ))
  </script>
</body>
</html>

Advanced: Custom Extensions

Create custom extensions in vanilla JavaScript:
import { Extension } from '@tiptap/core'

const CustomExtension = Extension.create({
  name: 'customExtension',
  
  addOptions() {
    return {
      myOption: 'default value',
    }
  },
  
  addCommands() {
    return {
      customCommand: () => ({ commands }) => {
        console.log('Custom command executed')
        return true
      },
    }
  },
  
  addKeyboardShortcuts() {
    return {
      'Mod-k': () => {
        console.log('Mod-k pressed')
        return true
      },
    }
  },
})

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [
    StarterKit,
    CustomExtension.configure({
      myOption: 'custom value',
    }),
  ],
})

// Use custom command
editor.commands.customCommand()

Cleanup

Always destroy the editor when it’s no longer needed:
const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

// Later, when you're done:
editor.destroy()

Using with Build Tools

// main.js
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import './style.css'

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

Next Steps

Extensions

Explore available extensions to enhance your editor

Commands

Learn about all available commands

Editor API

Full reference for the Editor class

Custom Extensions

Build your own extensions

Build docs developers (and LLMs) love