Skip to main content

Creating a Mark

Use the static create method to define a new mark.
import { Mark } from '@tiptap/core'

const MyMark = Mark.create<Options, Storage>({
  name: 'myMark',
  // ... configuration
})
config
Partial<MarkConfig<Options, Storage>> | (() => Partial<MarkConfig<Options, Storage>>)
The mark configuration object or a function that returns a configuration object.

Configuration Options

Marks inherit all configuration options from Extension, plus the following:

inclusive

Whether the mark should be active when the cursor is at the boundary.
inclusive?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['inclusive']
  editor?: Editor
}) => boolean)
inclusive
boolean
If true, the mark is active when the cursor is at its start/end. If false, it requires the cursor to be inside.
Example
inclusive: false // e.g., for link marks

excludes

Define which other marks this mark excludes.
excludes?: string | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['excludes']
  editor?: Editor
}) => string)
excludes
string
Space-separated mark names, or "_" to exclude all other marks.
Example
// Exclude all other marks
excludes: '_'

// Exclude specific marks
excludes: 'bold italic'

// Don't exclude any marks (default)
excludes: ''

exitable

Whether the mark can be exited at the end.
exitable?: boolean | (() => boolean)
exitable
boolean
default:"false"
If true, pressing right arrow at the end of the mark will move outside of it.
Example
exitable: true

group

Define the group(s) this mark belongs to.
group?: string | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['group']
  editor?: Editor
}) => string)
group
string
Space-separated group names.
Example
group: 'formatting'

spanning

Whether the mark spans across multiple nodes.
spanning?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['spanning']
  editor?: Editor
}) => boolean)
spanning
boolean
If false, the mark is removed when split.

code

Whether the mark represents code.
code?: boolean | ((this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['code']
  editor?: Editor
}) => boolean)
code
boolean
default:"false"
Indicates that this mark contains code.
Example
code: true

keepOnSplit

Whether to keep the mark after splitting a node.
keepOnSplit?: boolean | (() => boolean)
keepOnSplit
boolean
default:"true"
If true, the mark continues after splitting a text block.
Example
keepOnSplit: false

parseHTML()

Define how to parse HTML into this mark.
parseHTML?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['parseHTML']
  editor?: Editor
}): ParseRule[]
Example
parseHTML() {
  return [
    { tag: 'strong' },
    { tag: 'b', getAttrs: node => (node as HTMLElement).style.fontWeight !== 'normal' && null },
    { style: 'font-weight', getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value as string) && null },
  ]
}

renderHTML()

Define how to render the mark as HTML.
renderHTML?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['renderHTML']
  editor?: Editor
}, props: {
  mark: ProseMirrorMark
  HTMLAttributes: Record<string, any>
}): DOMOutputSpec
Example
renderHTML({ HTMLAttributes }) {
  return ['strong', HTMLAttributes, 0]
}

// With custom attributes
renderHTML({ mark, HTMLAttributes }) {
  return [
    'span',
    { ...HTMLAttributes, class: 'bold', 'data-weight': mark.attrs.weight },
    0, // 0 represents where content goes
  ]
}

addAttributes()

Define attributes for the mark.
addAttributes?(this: {
  name: string
  options: Options
  storage: Storage
  parent: ParentConfig['addAttributes']
  editor?: Editor
}): Attributes | {}
Example
addAttributes() {
  return {
    href: {
      default: null,
      parseHTML: element => element.getAttribute('href'),
      renderHTML: attributes => {
        if (!attributes.href) return {}
        return { href: attributes.href }
      },
    },
    target: {
      default: '_blank',
      parseHTML: element => element.getAttribute('target'),
      renderHTML: attributes => {
        if (!attributes.target) return {}
        return { target: attributes.target, rel: 'noopener noreferrer' }
      },
    },
    class: {
      default: null,
      parseHTML: element => element.getAttribute('class'),
      renderHTML: attributes => {
        if (!attributes.class) return {}
        return { class: attributes.class }
      },
    },
  }
}

addMarkView()

Define a custom mark view.
addMarkView?(this: {
  name: string
  options: Options
  storage: Storage
  editor: Editor
  type: MarkType
  parent: ParentConfig['addMarkView']
}): MarkViewRenderer
Example
addMarkView() {
  return ({ mark }) => {
    const span = document.createElement('span')
    span.classList.add('custom-mark')
    span.style.color = mark.attrs.color
    return span
  }
}

Methods

configure()

Create a configured version of the mark.
mark.configure(options?: Partial<Options>): Mark<Options, Storage>
options
Partial<Options>
Options to override the default options.
Example
import Link from '@tiptap/extension-link'

const editor = new Editor({
  extensions: [
    Link.configure({
      openOnClick: false,
      linkOnPaste: true,
    }),
  ],
})

extend()

Extend the mark with additional configuration.
mark.extend<ExtendedOptions, ExtendedStorage, ExtendedConfig>(
  extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)
): Mark<ExtendedOptions, ExtendedStorage>
extendedConfig
Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)
Additional configuration or a function that returns configuration.
Example
import Bold from '@tiptap/extension-bold'

const CustomBold = Bold.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      weight: {
        default: 'bold',
        parseHTML: element => element.style.fontWeight,
        renderHTML: attributes => {
          return { style: `font-weight: ${attributes.weight}` }
        },
      },
    }
  },
})

Complete Example

import { Mark, mergeAttributes } from '@tiptap/core'

interface HighlightOptions {
  multicolor: boolean
  HTMLAttributes: Record<string, any>
}

const Highlight = Mark.create<HighlightOptions>({
  name: 'highlight',

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

  addAttributes() {
    if (!this.options.multicolor) {
      return {}
    }

    return {
      color: {
        default: null,
        parseHTML: element => element.getAttribute('data-color') || element.style.backgroundColor,
        renderHTML: attributes => {
          if (!attributes.color) {
            return {}
          }

          return {
            'data-color': attributes.color,
            style: `background-color: ${attributes.color}; color: inherit`,
          }
        },
      },
    }
  },

  parseHTML() {
    return [
      {
        tag: 'mark',
      },
      {
        style: 'background-color',
        getAttrs: value => {
          // Don't match if the background color is the default one
          return value === 'transparent' ? false : null
        },
      },
    ]
  },

  renderHTML({ HTMLAttributes }) {
    return ['mark', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
  },

  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)
      },
    }
  },

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

// Usage
const editor = new Editor({
  extensions: [
    Highlight.configure({
      multicolor: true,
    }),
  ],
})

// Apply highlight
editor.commands.setHighlight({ color: '#ffc078' })

// Toggle highlight
editor.commands.toggleHighlight({ color: '#ffc078' })

// Remove highlight
editor.commands.unsetHighlight()

Build docs developers (and LLMs) love