> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# Vanilla JavaScript

> Learn how to use Tiptap with vanilla JavaScript without any framework

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

<Steps>
  <Step title="Install the core package">
    Install the core package and any extensions you need:

    ```bash theme={null}
    npm install @tiptap/core @tiptap/starter-kit
    ```
  </Step>

  <Step title="Import and use">
    Import the Editor class and extensions in your JavaScript:

    ```javascript theme={null}
    import { Editor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'
    ```
  </Step>
</Steps>

## Core Concepts

### Editor Class

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

#### Type Signature

```typescript theme={null}
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:

```javascript theme={null}
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

```html theme={null}
<!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

<CodeGroup>
  ```javascript Basic Editor theme={null}
  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>
    `,
  })
  ```

  ```javascript With Toolbar theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'

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

  // Create toolbar
  const toolbar = document.querySelector('#toolbar')

  // Bold button
  const boldButton = document.createElement('button')
  boldButton.textContent = 'Bold'
  boldButton.addEventListener('click', () => {
    editor.chain().focus().toggleBold().run()
  })
  toolbar.appendChild(boldButton)

  // Update button state on selection change
  editor.on('selectionUpdate', () => {
    if (editor.isActive('bold')) {
      boldButton.classList.add('is-active')
    } else {
      boldButton.classList.remove('is-active')
    }
  })

  // Italic button
  const italicButton = document.createElement('button')
  italicButton.textContent = 'Italic'
  italicButton.addEventListener('click', () => {
    editor.chain().focus().toggleItalic().run()
  })
  toolbar.appendChild(italicButton)

  editor.on('selectionUpdate', () => {
    if (editor.isActive('italic')) {
      italicButton.classList.add('is-active')
    } else {
      italicButton.classList.remove('is-active')
    }
  })
  ```

  ```javascript Minimal (Document + Paragraph + Text) theme={null}
  import Document from '@tiptap/extension-document'
  import Paragraph from '@tiptap/extension-paragraph'
  import Text from '@tiptap/extension-text'
  import { Editor } from '@tiptap/core'

  const editor = new Editor({
    element: document.querySelector('#editor'),
    extensions: [
      Document,
      Paragraph,
      Text,
    ],
    content: '<p>This is a minimal Tiptap editor.</p>',
  })
  ```

  ```javascript With Event Handlers theme={null}
  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>',
    
    onCreate: ({ editor }) => {
      console.log('Editor created', editor)
    },
    
    onUpdate: ({ editor }) => {
      console.log('Content updated')
      // Update preview
      document.querySelector('#output').innerHTML = editor.getHTML()
    },
    
    onSelectionUpdate: ({ editor }) => {
      console.log('Selection updated')
    },
    
    onFocus: ({ editor, event }) => {
      console.log('Editor focused')
    },
    
    onBlur: ({ editor, event }) => {
      console.log('Editor blurred')
    },
  })
  ```
</CodeGroup>

## Working with Content

### Getting Content

```javascript theme={null}
// 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

```javascript theme={null}
// 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:

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
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:

```html theme={null}
<!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:

```javascript theme={null}
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:

```javascript theme={null}
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

<Tabs>
  <Tab title="Vite">
    ```javascript theme={null}
    // 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>',
    })
    ```
  </Tab>

  <Tab title="Webpack">
    ```javascript theme={null}
    // index.js
    import { Editor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'

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

  <Tab title="CDN (Browser)">
    ```html theme={null}
    <!DOCTYPE html>
    <html>
    <head>
      <script type="module">
        import { Editor } from 'https://esm.sh/@tiptap/core'
        import StarterKit from 'https://esm.sh/@tiptap/starter-kit'
        
        window.addEventListener('DOMContentLoaded', () => {
          const editor = new Editor({
            element: document.querySelector('#editor'),
            extensions: [StarterKit],
            content: '<p>Hello World!</p>',
          })
        })
      </script>
    </head>
    <body>
      <div id="editor"></div>
    </body>
    </html>
    ```
  </Tab>
</Tabs>

## Next Steps

<CardGroup cols={2}>
  <Card title="Extensions" icon="puzzle-piece" href="/extensions">
    Explore available extensions to enhance your editor
  </Card>

  <Card title="Commands" icon="terminal" href="/api/commands">
    Learn about all available commands
  </Card>

  <Card title="Editor API" icon="code" href="/api/editor">
    Full reference for the Editor class
  </Card>

  <Card title="Custom Extensions" icon="wrench" href="/guide/custom-extensions">
    Build your own extensions
  </Card>
</CardGroup>
