Skip to main content
Marks in Yoopta Editor are text-level formatting that can be applied to individual characters or text ranges. You can create custom marks for highlighting, text colors, custom styles, and more.

Mark Anatomy

Marks are created using the createYooptaMark function:
import { createYooptaMark } from '@yoopta/editor';
import type { YooptaMarkProps } from '@yoopta/editor';

type MyMarkProps = YooptaMarkProps<'myMark', MyMarkValue>;

const MyMark = createYooptaMark<MyMarkProps>({
  type: 'myMark',      // Unique identifier
  hotkey: 'mod+m',     // Optional keyboard shortcut
  render: (props) => {  // Render function
    return <span className="my-mark">{props.children}</span>;
  },
});

Simple Mark Example

Let’s create a basic bold mark:
import { createYooptaMark, YooptaMarkProps } from '@yoopta/editor';

type BoldMarkProps = YooptaMarkProps<'bold', boolean>;

export const Bold = createYooptaMark<BoldMarkProps>({
  type: 'bold',
  hotkey: 'mod+b',
  render: (props) => (
    <strong className="yoopta-mark-bold">
      {props.children}
    </strong>
  ),
});

Mark with Values

Create marks with custom values like colors:
type HighlightValue = {
  color: string;
  backgroundColor: string;
};

type HighlightMarkProps = YooptaMarkProps<'highlight', HighlightValue>;

export const Highlight = createYooptaMark<HighlightMarkProps>({
  type: 'highlight',
  render: (props) => {
    const { leaf } = props;
    const value = leaf.highlight;
    
    if (!value) return <>{props.children}</>;
    
    return (
      <span
        className="yoopta-mark-highlight"
        style={{
          color: value.color,
          backgroundColor: value.backgroundColor,
        }}
      >
        {props.children}
      </span>
    );
  },
});

Mark Props Type

The YooptaMarkProps type provides the structure for mark render props:
type YooptaMarkProps<K extends string, V> = {
  children: React.ReactNode;
  leaf: ExtendedLeaf<K, V>;
};

type ExtendedLeaf<K extends string, V> = {
  text: string;
  // The mark's value
  [key in K]: V;
  // Other marks may be present
  bold?: boolean;
  italic?: boolean;
  // etc...
};

Keyboard Shortcuts

Marks can have keyboard shortcuts:
const Bold = createYooptaMark({
  type: 'bold',
  hotkey: 'mod+b',     // Cmd+B on Mac, Ctrl+B on Windows
  render: (props) => <strong>{props.children}</strong>,
});

const Italic = createYooptaMark({
  type: 'italic',
  hotkey: 'mod+i',     // Cmd+I on Mac, Ctrl+I on Windows
  render: (props) => <em>{props.children}</em>,
});

const Underline = createYooptaMark({
  type: 'underline',
  hotkey: 'mod+u',     // Cmd+U on Mac, Ctrl+U on Windows
  render: (props) => <u>{props.children}</u>,
});
The mod key automatically maps to:
  • Cmd on macOS
  • Ctrl on Windows/Linux

Using Marks

Add marks to your editor:
import { createYooptaEditor } from '@yoopta/editor';
import { Bold, Italic, Underline, Highlight } from './marks';

const editor = createYooptaEditor({
  plugins: PLUGINS,
  marks: [Bold, Italic, Underline, Highlight],
});

Marks API

Use the Marks namespace to interact with marks:
import { Marks } from '@yoopta/editor';

// Toggle a boolean mark
Marks.toggle(editor, { type: 'bold' });
Marks.toggle(editor, { type: 'italic' });

// Check if mark is active
const isBold = Marks.isActive(editor, { type: 'bold' });

// Add a mark with value
Marks.add(editor, {
  type: 'highlight',
  value: {
    color: '#000000',
    backgroundColor: '#FFFF00',
  },
});

// Update mark value
Marks.update(editor, {
  type: 'highlight',
  value: {
    color: '#FFFFFF',
    backgroundColor: '#FF0000',
  },
});

// Remove a mark
Marks.remove(editor, { type: 'highlight' });

// Remove all marks
Marks.clear(editor, {});

Built-in Marks

Yoopta provides common marks in the @yoopta/marks package:
import {
  Bold,
  Italic,
  Underline,
  Strike,
  CodeMark,
  Highlight,
} from '@yoopta/marks';

const MARKS = [
  Bold,        // mod+b
  Italic,      // mod+i
  Underline,   // mod+u
  Strike,      // mod+shift+s
  CodeMark,    // mod+e
  Highlight,   // Custom color highlighting
];

Complex Mark Examples

Text Color Mark

type TextColorValue = string; // hex color

type TextColorMarkProps = YooptaMarkProps<'textColor', TextColorValue>;

export const TextColor = createYooptaMark<TextColorMarkProps>({
  type: 'textColor',
  render: (props) => {
    const color = props.leaf.textColor;
    
    if (!color) return <>{props.children}</>;
    
    return (
      <span style={{ color }}>
        {props.children}
      </span>
    );
  },
});

Font Size Mark

type FontSizeValue = number; // in pixels

type FontSizeMarkProps = YooptaMarkProps<'fontSize', FontSizeValue>;

export const FontSize = createYooptaMark<FontSizeMarkProps>({
  type: 'fontSize',
  render: (props) => {
    const size = props.leaf.fontSize;
    
    if (!size) return <>{props.children}</>;
    
    return (
      <span style={{ fontSize: `${size}px` }}>
        {props.children}
      </span>
    );
  },
});

Custom Class Mark

type ClassNameValue = string;

type ClassNameMarkProps = YooptaMarkProps<'className', ClassNameValue>;

export const ClassName = createYooptaMark<ClassNameMarkProps>({
  type: 'className',
  render: (props) => {
    const className = props.leaf.className;
    
    if (!className) return <>{props.children}</>;
    
    return (
      <span className={className}>
        {props.children}
      </span>
    );
  },
});

Tooltip Mark

type TooltipValue = string; // tooltip text

type TooltipMarkProps = YooptaMarkProps<'tooltip', TooltipValue>;

export const Tooltip = createYooptaMark<TooltipMarkProps>({
  type: 'tooltip',
  render: (props) => {
    const tooltip = props.leaf.tooltip;
    
    if (!tooltip) return <>{props.children}</>;
    
    return (
      <abbr title={tooltip} className="tooltip">
        {props.children}
      </abbr>
    );
  },
});

Creating a Toolbar for Marks

import { Marks } from '@yoopta/editor';
import { useYooptaEditor } from '@yoopta/editor';

function MarkToolbar() {
  const editor = useYooptaEditor();
  
  const toggleBold = () => Marks.toggle(editor, { type: 'bold' });
  const toggleItalic = () => Marks.toggle(editor, { type: 'italic' });
  const toggleUnderline = () => Marks.toggle(editor, { type: 'underline' });
  
  const isBold = Marks.isActive(editor, { type: 'bold' });
  const isItalic = Marks.isActive(editor, { type: 'italic' });
  const isUnderline = Marks.isActive(editor, { type: 'underline' });
  
  return (
    <div className="mark-toolbar">
      <button
        onClick={toggleBold}
        className={isBold ? 'active' : ''}
      >
        Bold
      </button>
      <button
        onClick={toggleItalic}
        className={isItalic ? 'active' : ''}
      >
        Italic
      </button>
      <button
        onClick={toggleUnderline}
        className={isUnderline ? 'active' : ''}
      >
        Underline
      </button>
    </div>
  );
}

Color Picker Example

import { Marks } from '@yoopta/editor';
import { useState } from 'react';

function ColorPicker() {
  const editor = useYooptaEditor();
  const [color, setColor] = useState('#000000');
  const [bgColor, setBgColor] = useState('#FFFF00');
  
  const applyHighlight = () => {
    Marks.add(editor, {
      type: 'highlight',
      value: {
        color,
        backgroundColor: bgColor,
      },
    });
  };
  
  const removeHighlight = () => {
    Marks.remove(editor, { type: 'highlight' });
  };
  
  return (
    <div>
      <input
        type="color"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      <input
        type="color"
        value={bgColor}
        onChange={(e) => setBgColor(e.target.value)}
      />
      <button onClick={applyHighlight}>Apply</button>
      <button onClick={removeHighlight}>Remove</button>
    </div>
  );
}

Mark Serialization

Marks are automatically serialized by the editor’s parser:
// Text with marks
const text = [
  { text: 'Normal ' },
  { text: 'bold', bold: true },
  { text: ' and ' },
  { text: 'italic', italic: true },
];

// HTML output
// "Normal <strong>bold</strong> and <em>italic</em>"

// Markdown output
// "Normal **bold** and _italic_"

Custom Serialization

For custom marks, you’ll need to handle serialization in text node serializers:
function serializeCustomTextNodes(children: any[]) {
  return children.map(child => {
    if (typeof child === 'string') return child;
    
    let text = child.text;
    
    if (child.bold) text = `<strong>${text}</strong>`;
    if (child.italic) text = `<em>${text}</em>`;
    if (child.highlight) {
      const { color, backgroundColor } = child.highlight;
      text = `<span style="color: ${color}; background-color: ${backgroundColor}">${text}</span>`;
    }
    
    return text;
  }).join('');
}

Best Practices

Marks are applied to individual characters, so keep rendering logic simple:
// Good
render: (props) => <strong>{props.children}</strong>

// Avoid heavy computations or side effects
render: (props) => {
  // Don't do this
  const expensiveValue = computeExpensiveValue();
  return <strong>{props.children}</strong>;
}
Use semantic elements for better accessibility:
// Good
<strong>{children}</strong>  // Bold
<em>{children}</em>          // Italic
<mark>{children}</mark>      // Highlight

// Avoid
<span style="font-weight: bold">{children}</span>
Always provide TypeScript types:
type HighlightValue = {
  color: string;
  backgroundColor: string;
};

type HighlightMarkProps = YooptaMarkProps<'highlight', HighlightValue>;
Always check if the mark value exists:
render: (props) => {
  const value = props.leaf.myMark;
  if (!value) return <>{props.children}</>;
  // Use value
}

Testing Custom Marks

import { describe, it, expect } from 'vitest';
import { Marks } from '@yoopta/editor';
import { createYooptaEditor } from '@yoopta/editor';
import MyMark from './my-mark';

describe('MyMark', () => {
  it('should toggle mark', () => {
    const editor = createYooptaEditor({
      plugins: [],
      marks: [MyMark],
    });
    
    Marks.toggle(editor, { type: 'myMark' });
    
    const isActive = Marks.isActive(editor, { type: 'myMark' });
    expect(isActive).toBe(true);
  });
  
  it('should add mark with value', () => {
    const editor = createYooptaEditor({
      plugins: [],
      marks: [MyMark],
    });
    
    Marks.add(editor, {
      type: 'myMark',
      value: { color: '#FF0000' },
    });
    
    // Test that mark was applied
  });
});

Build docs developers (and LLMs) love