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 APIeditor.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.