Find out what's new in Tiptap Editor 3.0

Create a new mark

Creating a Mark Extension

Marks are used to add inline formatting to text in Tiptap. Common examples include bold, italic, and underline formatting. Let's learn how to create our own mark extension step by step.

The options available can be found in the Mark API.

Basic Structure

First, we need to create the basic structure of a mark extension:

// filepath: src/HighlightMark.ts
import { Mark } from '@tiptap/core'

const HighlightMark = Mark.create({
  name: 'highlight',

  addOptions() {
    return {
      HTMLAttributes: {},
    }
  },
})

export default HighlightMark

What we've done here is:

  • Created a new mark extension named HighlightMark
  • Added an addOptions method to define the mark's options which are configurable by the user

Adding Styling

Let's add styling capabilities by defining how our mark renders and parses HTML:

// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
  // ...existing code...

  parseHTML() {
    return [
      {
        tag: 'mark',
      },
    ]
  },

  renderHTML({ HTMLAttributes }) {
    return ['mark', HTMLAttributes, 0]
  },
})

Adding Attributes

We can make our mark more flexible by adding customizable attributes:

// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
  // ...existing code...

  addAttributes() {
    return {
      color: {
        default: 'yellow',
        parseHTML: (element) => element.getAttribute('data-color'),
        renderHTML: (attributes) => ({
          'data-color': attributes.color,
          style: `background-color: ${attributes.color}`,
        }),
      },
    }
  },
})

Adding Commands

Make the mark interactive with commands:

// filepath: src/HighlightMark.ts

// We need to extend the Commands interface to add our custom commands to the editor
declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    setHighlight: (attributes: { color: string }) => ReturnType
    toggleHighlight: (attributes: { color: string }) => ReturnType
    unsetHighlight: () => ReturnType
  }
}

const HighlightMark = Mark.create({
  // ...existing code...

  addCommands() {
    return {
      setHighlight:
        (attributes) =>
        ({ commands }) => {
          return commands.setMark(this.name, attributes)
        },
      toggleHighlight:
        (attributes) =>
        ({ commands }) => {
          return commands.toggleMark(this.name, attributes)
        },
      unsetHighlight:
        () =>
        ({ commands }) => {
          return commands.unsetMark(this.name)
        },
    }
  },
})

This adds commands which are available on the editor instance like:

  • editor.commands.setHighlight({ color: 'pink' }) Using the commands API
  • editor.chain().toggleHighlight().run() Using the chaining API

Adding Keyboard Shortcuts

Add keyboard shortcuts for quick formatting:

// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
  // ...existing code...

  addKeyboardShortcuts() {
    return {
      'Mod-h': () => this.editor.commands.toggleHighlight(),
    }
  },
})

Adding Input Rules

Support Markdown-style syntax:

// filepath: src/HighlightMark.ts
import { markInputRule } from '@tiptap/core'

const HighlightMark = Mark.create({
  // ...existing code...

  addInputRules() {
    return [
      markInputRule({
        find: /(?:==)((?:[^=]+))(?:==)$/,
        type: this.type,
      }),
    ]
  },
})

Using the Mark

Here's how to use your new mark extension:

// filepath: src/Editor.ts
import { Editor } from '@tiptap/core'
import HighlightMark from './HighlightMark'

new Editor({
  extensions: [
    HighlightMark,
    // ... other extensions
  ],
})

Now you can:

  • Use ==text== to highlight text (input rule)
  • Press Cmd+H (Ctrl+H on Windows) to toggle highlighting
  • Programmatically control highlighting:
    editor.commands.setHighlight({ color: 'pink' })
    editor.commands.toggleHighlight()
    editor.commands.unsetHighlight()

Testing

Create tests to ensure your mark works as expected:

// filepath: src/HighlightMark.test.ts
import { Editor } from '@tiptap/core'
import HighlightMark from './HighlightMark'

describe('HighlightMark', () => {
  let editor: Editor

  beforeEach(() => {
    editor = new Editor({
      extensions: [HighlightMark],
      content: '',
    })
  })

  test('can toggle highlight mark', () => {
    editor.commands.setContent('test')
    editor.commands.selectAll()
    editor.commands.toggleHighlight()

    expect(editor.getHTML()).toBe('<mark>test</mark>')
  })
})

This mark extension provides a foundation that you can build upon for your specific use case. You can extend it further by adding more attributes, commands, or changing how it renders in the editor.