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