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)
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)
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)
If true, pressing right arrow at the end of the mark will move outside of it.
Example
group
Define the group(s) this mark belongs to.
group?: string | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['group']
editor?: Editor
}) => string)
Space-separated group names.
Example
spanning
Whether the mark spans across multiple nodes.
spanning?: boolean | ((this: {
name: string
options: Options
storage: Storage
parent: ParentConfig['spanning']
editor?: Editor
}) => 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)
Indicates that this mark contains code.
Example
keepOnSplit
Whether to keep the mark after splitting a node.
keepOnSplit?: boolean | (() => boolean)
If true, the mark continues after splitting a text block.
Example
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
Create a configured version of the mark.
mark.configure(options?: Partial<Options>): Mark<Options, Storage>
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()