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

# Custom Marks

> Learn how to create custom marks to add inline formatting to Tiptap

# Custom Marks

Marks are inline styles that can be applied to text, like bold, italic, underline, or links. Unlike nodes which are structural, marks are formatting that can be toggled on and off for portions of text.

## Understanding Marks

The Mark class extends the Extendable class and provides functionality for inline formatting.

<CodeGroup>
  ```typescript Mark API theme={null}
  import { Mark } from '@tiptap/core'

  export const MyMark = Mark.create({
    name: 'myMark',
    
    // Parse from HTML
    parseHTML() {
      return [{ tag: 'span' }]
    },
    
    // Render to HTML
    renderHTML({ HTMLAttributes }) {
      return ['span', HTMLAttributes, 0]
    },
    
    // Add attributes
    addAttributes() {
      return {}
    },
    
    // Add commands
    addCommands() {
      return {}
    },
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:146`

## Creating a Simple Mark

Let's start with the bold mark.

<CodeGroup>
  ```typescript Bold Mark theme={null}
  import { Mark, markInputRule, markPasteRule, mergeAttributes } from '@tiptap/core'

  export interface BoldOptions {
    HTMLAttributes: Record<string, any>
  }

  declare module '@tiptap/core' {
    interface Commands<ReturnType> {
      bold: {
        setBold: () => ReturnType
        toggleBold: () => ReturnType
        unsetBold: () => ReturnType
      }
    }
  }

  export const Bold = Mark.create<BoldOptions>({
    name: 'bold',
    
    addOptions() {
      return {
        HTMLAttributes: {},
      }
    },
    
    parseHTML() {
      return [
        { tag: 'strong' },
        { 
          tag: 'b', 
          getAttrs: node => (node as HTMLElement).style.fontWeight !== 'normal' && null 
        },
        {
          style: 'font-weight=400',
          clearMark: mark => mark.type.name === this.name,
        },
        {
          style: 'font-weight',
          getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value as string) && null,
        },
      ]
    },
    
    renderHTML({ HTMLAttributes }) {
      return ['strong', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
    },
    
    addCommands() {
      return {
        setBold: () => ({ commands }) => {
          return commands.setMark(this.name)
        },
        toggleBold: () => ({ commands }) => {
          return commands.toggleMark(this.name)
        },
        unsetBold: () => ({ commands }) => {
          return commands.unsetMark(this.name)
        },
      }
    },
    
    addKeyboardShortcuts() {
      return {
        'Mod-b': () => this.editor.commands.toggleBold(),
        'Mod-B': () => this.editor.commands.toggleBold(),
      }
    },
    
    addInputRules() {
      return [
        markInputRule({
          find: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/,
          type: this.type,
        }),
        markInputRule({
          find: /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/,
          type: this.type,
        }),
      ]
    },
    
    addPasteRules() {
      return [
        markPasteRule({
          find: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g,
          type: this.type,
        }),
        markPasteRule({
          find: /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g,
          type: this.type,
        }),
      ]
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-bold/src/bold.tsx:56`

## Mark with Attributes

Here's the highlight mark that demonstrates using attributes.

<CodeGroup>
  ```typescript Highlight Mark theme={null}
  import { Mark, markInputRule, markPasteRule, mergeAttributes } from '@tiptap/core'

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

  declare module '@tiptap/core' {
    interface Commands<ReturnType> {
      highlight: {
        setHighlight: (attributes?: { color: string }) => ReturnType
        toggleHighlight: (attributes?: { color: string }) => ReturnType
        unsetHighlight: () => ReturnType
      }
    }
  }

  export 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' }]
    },
    
    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(),
      }
    },
    
    addInputRules() {
      return [
        markInputRule({
          find: /(?:^|\s)(==(?!\s+==)((?:[^=]+))==(?!\s+==))$/,
          type: this.type,
        }),
      ]
    },
    
    addPasteRules() {
      return [
        markPasteRule({
          find: /(?:^|\s)(==(?!\s+==)((?:[^=]+))==(?!\s+==))/g,
          type: this.type,
        }),
      ]
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-highlight/src/highlight.ts:57`

## Simple Italic Mark

The italic mark shows a minimal implementation.

<CodeGroup>
  ```typescript Italic Mark theme={null}
  import { Mark, markInputRule, markPasteRule, mergeAttributes } from '@tiptap/core'

  export interface ItalicOptions {
    HTMLAttributes: Record<string, any>
  }

  declare module '@tiptap/core' {
    interface Commands<ReturnType> {
      italic: {
        setItalic: () => ReturnType
        toggleItalic: () => ReturnType
        unsetItalic: () => ReturnType
      }
    }
  }

  export const Italic = Mark.create<ItalicOptions>({
    name: 'italic',
    
    addOptions() {
      return {
        HTMLAttributes: {},
      }
    },
    
    parseHTML() {
      return [
        { tag: 'em' },
        { 
          tag: 'i', 
          getAttrs: node => (node as HTMLElement).style.fontStyle !== 'normal' && null 
        },
        {
          style: 'font-style=normal',
          clearMark: mark => mark.type.name === this.name,
        },
        {
          style: 'font-style=italic',
        },
      ]
    },
    
    renderHTML({ HTMLAttributes }) {
      return ['em', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
    },
    
    addCommands() {
      return {
        setItalic: () => ({ commands }) => {
          return commands.setMark(this.name)
        },
        toggleItalic: () => ({ commands }) => {
          return commands.toggleMark(this.name)
        },
        unsetItalic: () => ({ commands }) => {
          return commands.unsetMark(this.name)
        },
      }
    },
    
    addKeyboardShortcuts() {
      return {
        'Mod-i': () => this.editor.commands.toggleItalic(),
        'Mod-I': () => this.editor.commands.toggleItalic(),
      }
    },
    
    addInputRules() {
      return [
        markInputRule({
          find: /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/,
          type: this.type,
        }),
        markInputRule({
          find: /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))$/,
          type: this.type,
        }),
      ]
    },
    
    addPasteRules() {
      return [
        markPasteRule({
          find: /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))/g,
          type: this.type,
        }),
        markPasteRule({
          find: /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))/g,
          type: this.type,
        }),
      ]
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-italic/src/italic.ts:58`

## Mark Properties

Marks have several important properties that control their behavior.

<CodeGroup>
  ```typescript Mark Properties theme={null}
  import { Mark } from '@tiptap/core'

  export const MyMark = Mark.create({
    name: 'myMark',
    
    // Mark remains after splitting node (like bold)
    keepOnSplit: true,
    
    // Mark is inclusive at boundaries
    inclusive: true,
    
    // Can be exited with arrow keys
    exitable: true,
    
    // This mark excludes other marks
    excludes: 'link code',
    
    // Belongs to mark group
    group: 'formatting',
    
    // Mark spans across nodes
    spanning: true,
    
    // Represents code
    code: false,
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:25`

## Mark Attributes

Attributes allow marks to store additional data.

<CodeGroup>
  ```typescript Mark Attributes theme={null}
  import { Mark } from '@tiptap/core'

  export const Link = Mark.create({
    name: 'link',
    
    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 => ({
            target: attributes.target,
            rel: 'noopener noreferrer nofollow',
          }),
        },
        class: {
          default: 'link',
          parseHTML: element => element.getAttribute('class'),
          renderHTML: attributes => ({
            class: attributes.class,
          }),
        },
      }
    },
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:130`

## Mark Input Rules

Input rules automatically apply marks based on text patterns.

<CodeGroup>
  ```typescript Mark Input Rules theme={null}
  import { Mark, markInputRule } from '@tiptap/core'

  export const Bold = Mark.create({
    name: 'bold',
    
    addInputRules() {
      return [
        // Matches **bold** syntax
        markInputRule({
          find: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/,
          type: this.type,
        }),
        // Matches __bold__ syntax
        markInputRule({
          find: /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/,
          type: this.type,
        }),
      ]
    },
  })
  ```
</CodeGroup>

## Mark Paste Rules

Paste rules apply marks when content is pasted.

<CodeGroup>
  ```typescript Mark Paste Rules theme={null}
  import { Mark, markPasteRule } from '@tiptap/core'

  export const Bold = Mark.create({
    name: 'bold',
    
    addPasteRules() {
      return [
        markPasteRule({
          find: /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g,
          type: this.type,
        }),
        markPasteRule({
          find: /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g,
          type: this.type,
        }),
      ]
    },
  })
  ```
</CodeGroup>

## Exitable Marks

Some marks should be easy to exit with arrow keys.

<CodeGroup>
  ```typescript Exitable Mark theme={null}
  import { Mark } from '@tiptap/core'

  export const Link = Mark.create({
    name: 'link',
    
    exitable: true,
    
    addKeyboardShortcuts() {
      return {
        // Exit link with arrow right at end
        ArrowRight: () => Mark.handleExit({ 
          editor: this.editor, 
          mark: this 
        }),
      }
    },
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:159`

## Mark Exclusion

Some marks cannot coexist with others.

<CodeGroup>
  ```typescript Mark Exclusion theme={null}
  import { Mark } from '@tiptap/core'

  // Code mark excludes all other formatting
  export const Code = Mark.create({
    name: 'code',
    excludes: '_', // Excludes all marks
  })

  // Link excludes other links
  export const Link = Mark.create({
    name: 'link',
    excludes: 'link', // Can't nest links
  })

  // Custom exclusion
  export const Highlight = Mark.create({
    name: 'highlight',
    excludes: 'highlight link', // Excludes highlight and link
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:42`

## TypeScript Commands

Add type-safe commands to your mark.

<CodeGroup>
  ```typescript Typed Commands theme={null}
  import { Mark } from '@tiptap/core'

  export interface HighlightOptions {
    multicolor: boolean
  }

  declare module '@tiptap/core' {
    interface Commands<ReturnType> {
      highlight: {
        setHighlight: (attributes?: { color: string }) => ReturnType
        toggleHighlight: (attributes?: { color: string }) => ReturnType
        unsetHighlight: () => ReturnType
      }
    }
  }

  export const Highlight = Mark.create<HighlightOptions>({
    name: 'highlight',
    
    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)
        },
      }
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-highlight/src/highlight.ts:19`

## Extending Marks

You can extend existing marks to customize behavior.

<CodeGroup>
  ```typescript Extend Mark theme={null}
  import { Bold } from '@tiptap/extension-bold'

  export const CustomBold = Bold.extend({
    addOptions() {
      return {
        ...this.parent?.(),
        HTMLAttributes: {
          class: 'custom-bold',
        },
      }
    },
    
    addKeyboardShortcuts() {
      return {
        ...this.parent?.(),
        'Mod-Shift-b': () => this.editor.commands.toggleBold(),
      }
    },
  })
  ```
</CodeGroup>

Source: `packages/core/src/Mark.ts:191`

<Note>
  Marks are ideal for inline formatting that can be toggled on and off. Use Nodes for block-level content and Extensions for editor-wide functionality.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Nodes" icon="cube" href="/guides/custom-nodes">
    Create custom block content
  </Card>

  <Card title="Styling" icon="palette" href="/guides/styling">
    Style your editor and content
  </Card>
</CardGroup>
