Skip to main content
Marks are text-level formatting attributes like bold, italic, underline, and highlight. Unlike blocks and elements which define structure, marks define the visual appearance of text content.

What are Marks?

Marks are properties attached to text nodes that control their formatting:
packages/core/editor/src/editor/types.ts
type SlateElementTextNode = {
  text: string;
  bold?: boolean;
  italic?: boolean;
  underline?: boolean;
  code?: boolean;
  strike?: boolean;
  highlight?: any;
};
Marks follow the Slate.js text formatting model. They’re stored on leaf text nodes rather than as wrapper elements.

Creating Marks

Marks are created using the createYooptaMark function:
packages/core/editor/src/marks/index.ts
import { createYooptaMark } from '@yoopta/editor';

const Bold = createYooptaMark({
  type: 'bold',
  hotkey: 'mod+b',
  render: ({ children, leaf }) => (
    <strong>{children}</strong>
  ),
});

Mark Configuration

packages/core/editor/src/marks/index.ts
type YooptaMarkParams<TProps> = {
  type: string;                        // Mark identifier
  hotkey?: string;                     // Keyboard shortcut
  render: (props: TProps) => JSX.Element;  // Rendering function
};
PropertyTypeRequiredDescription
typestringYesUnique mark identifier (e.g., ‘bold’, ‘italic’)
hotkeystringNoKeyboard shortcut (e.g., ‘mod+b’ for Ctrl/Cmd+B)
renderfunctionYesReact component to render marked text
The mod key in hotkeys refers to Ctrl on Windows/Linux and Cmd on macOS.

Built-in Marks

Yoopta Editor provides common text marks through @yoopta/marks:
import { Bold, Italic, Underline, Strike, Code, Highlight } from '@yoopta/marks';

const MARKS = [Bold, Italic, Underline, Strike, Code, Highlight];

const editor = createYooptaEditor({
  plugins: PLUGINS,
  marks: MARKS,
  value: initialValue,
});

Available Built-in Marks

MarkHotkeyRendered AsUsage
BoldCmd/Ctrl+B<strong>Make text bold
ItalicCmd/Ctrl+I<em>Make text italic
UnderlineCmd/Ctrl+U<u>Underline text
StrikeCmd/Ctrl+Shift+S<s>Strikethrough text
CodeCmd/Ctrl+E<code>Inline code
HighlightNone<mark>Highlight text

Using Marks in Code

Creating Text with Marks

Use the editor.y.text() builder method:
// Plain text
editor.y.text('Hello world')

// Bold text
editor.y.text('Bold text', { bold: true })

// Multiple marks
editor.y.text('Bold and italic', { 
  bold: true, 
  italic: true 
})

// All formatting
editor.y.text('Fully formatted', {
  bold: true,
  italic: true,
  underline: true,
  strike: true,
  code: true,
  highlight: {
    color: '#000000',
    backgroundColor: '#ffff00'
  }
})

Creating Elements with Formatted Text

// Paragraph with mixed formatting
const paragraph = editor.y('paragraph', {
  children: [
    editor.y.text('This is '),
    editor.y.text('bold', { bold: true }),
    editor.y.text(' and '),
    editor.y.text('italic', { italic: true }),
    editor.y.text(' text.'),
  ]
});

// Insert into editor
editor.insertBlock('Paragraph', {
  elements: paragraph,
  focus: true
});

Mark Operations

Updating Marks

Use the Marks namespace to apply formatting:
import { Marks } from '@yoopta/editor';

// Apply bold to current selection
Marks.update(editor, {
  type: 'bold',
  value: true
});

// Apply highlight with color
Marks.update(editor, {
  type: 'highlight',
  value: {
    color: '#000000',
    backgroundColor: '#ffff00'
  }
});

// Apply to specific blocks
Marks.update(editor, {
  type: 'italic',
  value: true,
  at: [0, 1, 2], // Block indices
});

Toggling Marks

Marks can be toggled on and off:
// Toggle bold (turns on if off, off if on)
Marks.toggle(editor, { type: 'bold' });

// Toggle with specific value
Marks.toggle(editor, { 
  type: 'highlight',
  value: { backgroundColor: '#ffff00' }
});

Removing Marks

Set mark value to false or null:
// Remove bold
Marks.update(editor, {
  type: 'bold',
  value: false
});

// Remove highlight
Marks.update(editor, {
  type: 'highlight',
  value: null
});

Checking Active Marks

Access mark state through the editor’s formats:
// Get mark format interface
const boldFormat = editor.formats.bold;

if (boldFormat) {
  const isActive = boldFormat.isActive();
  const value = boldFormat.getValue();
  
  console.log('Bold is active:', isActive);
  console.log('Bold value:', value);
}

Creating Custom Marks

You can create custom marks for specialized formatting:
import { createYooptaMark } from '@yoopta/editor';

// Custom superscript mark
const Superscript = createYooptaMark({
  type: 'superscript',
  hotkey: 'mod+.',
  render: ({ children }) => (
    <sup style={{ fontSize: '0.75em', verticalAlign: 'super' }}>
      {children}
    </sup>
  ),
});

// Custom subscript mark
const Subscript = createYooptaMark({
  type: 'subscript',
  hotkey: 'mod+,',
  render: ({ children }) => (
    <sub style={{ fontSize: '0.75em', verticalAlign: 'sub' }}>
      {children}
    </sub>
  ),
});

// Custom color mark
type ColorMarkProps = {
  children: React.ReactNode;
  leaf: {
    color?: string;
  };
};

const Color = createYooptaMark<ColorMarkProps>({
  type: 'color',
  render: ({ children, leaf }) => (
    <span style={{ color: leaf.color }}>
      {children}
    </span>
  ),
});

Using Custom Marks

const MARKS = [
  Bold,
  Italic,
  Underline,
  Superscript,
  Subscript,
  Color,
];

const editor = createYooptaEditor({
  plugins: PLUGINS,
  marks: MARKS,
  value: initialValue,
});

// Use in code
editor.y.text('H', { superscript: true });
editor.y.text('2', { subscript: true });
editor.y.text('O');

// Apply via Marks API
Marks.update(editor, {
  type: 'color',
  value: '#ff0000'
});

Mark Rendering

Marks receive props for rendering:
type YooptaMarkProps<K extends string, V> = {
  children: React.ReactNode;  // The marked text
  leaf: ExtendedLeaf<K, V>;   // Leaf node with mark data
};

type ExtendedLeaf<K extends string, V> = {
  text: string;
  [markType]: V;  // Mark-specific data
  withPlaceholder?: boolean;
  elementPlaceholder?: string;
};

Accessing Mark Data

const Highlight = createYooptaMark({
  type: 'highlight',
  render: ({ children, leaf }) => {
    const { highlight } = leaf;
    
    if (!highlight) return children;
    
    return (
      <mark 
        style={{
          backgroundColor: highlight.backgroundColor,
          color: highlight.color,
        }}
      >
        {children}
      </mark>
    );
  },
});

Mark Composition

Multiple marks can be applied to the same text:
// Text with multiple marks
editor.y.text('Important', {
  bold: true,
  italic: true,
  underline: true,
  highlight: {
    backgroundColor: '#ffff00'
  }
})

// Renders as:
// <strong><em><u><mark>Important</mark></u></em></strong>
Be careful with mark composition as too many nested marks can impact rendering performance. Limit to 3-4 marks per text node when possible.

Hotkeys

Hotkeys are automatically registered when marks are configured:
import { HOTKEYS } from '@yoopta/editor';

// Check if hotkey matches
const isBoldHotkey = HOTKEYS.isBold(event);
const isItalicHotkey = HOTKEYS.isItalic(event);
Available hotkey helpers:
  • HOTKEYS.isBold(event) - Cmd/Ctrl+B
  • HOTKEYS.isItalic(event) - Cmd/Ctrl+I
  • HOTKEYS.isUnderline(event) - Cmd/Ctrl+U
  • HOTKEYS.isStrike(event) - Cmd/Ctrl+Shift+S
  • HOTKEYS.isCode(event) - Cmd/Ctrl+E

Mark Formats API

The editor exposes a formats object with methods for each mark:
packages/core/editor/src/editor/types.ts
type TextFormat = {
  type: string;
  hotkey?: string;
  getValue: () => null | any;
  isActive: () => boolean;
  toggle: () => void;
  update: (props?: any) => void;
};

type YooptaFormats = Record<string, TextFormat>;

Using Format Methods

// Access format
const bold = editor.formats.bold;

// Check if active
if (bold.isActive()) {
  console.log('Bold is active at current selection');
}

// Get current value
const value = bold.getValue();

// Toggle
bold.toggle();

// Update with value
bold.update(true);

Building a Formatting Toolbar

Here’s an example of a formatting toolbar using marks:
import { useYooptaEditor } from '@yoopta/editor';

function FormattingToolbar() {
  const editor = useYooptaEditor();
  
  const toggleMark = (markType: string) => {
    const format = editor.formats[markType];
    if (format) {
      format.toggle();
    }
  };
  
  const isActive = (markType: string) => {
    const format = editor.formats[markType];
    return format?.isActive() ?? false;
  };
  
  return (
    <div className="toolbar">
      <button 
        onClick={() => toggleMark('bold')}
        className={isActive('bold') ? 'active' : ''}
      >
        <strong>B</strong>
      </button>
      
      <button 
        onClick={() => toggleMark('italic')}
        className={isActive('italic') ? 'active' : ''}
      >
        <em>I</em>
      </button>
      
      <button 
        onClick={() => toggleMark('underline')}
        className={isActive('underline') ? 'active' : ''}
      >
        <u>U</u>
      </button>
      
      <button 
        onClick={() => toggleMark('strike')}
        className={isActive('strike') ? 'active' : ''}
      >
        <s>S</s>
      </button>
      
      <button 
        onClick={() => toggleMark('code')}
        className={isActive('code') ? 'active' : ''}
      >
        <code>{'</>'}</code>
      </button>
    </div>
  );
}

Mark Serialization

Marks are preserved when serializing to HTML or Markdown:
// HTML serialization
const html = editor.getHTML(editor.children);
// <p>This is <strong>bold</strong> and <em>italic</em> text.</p>

// Markdown serialization
const markdown = editor.getMarkdown(editor.children);
// This is **bold** and *italic* text.

Best Practices

1. Keep Mark Logic Simple

Marks should focus on visual formatting only, not behavior:
// Good: Simple formatting
const Bold = createYooptaMark({
  type: 'bold',
  render: ({ children }) => <strong>{children}</strong>,
});

// Bad: Adding behavior in marks
const Bold = createYooptaMark({
  type: 'bold',
  render: ({ children }) => (
    <strong onClick={handleClick}>{children}</strong>
  ),
});

2. Use Consistent Naming

Mark types should be lowercase and descriptive:
// Good
type: 'bold'
type: 'italic'
type: 'underline'
type: 'highlight'

// Bad
type: 'Bold'
type: 'ITALIC'
type: 'under_line'

3. Provide Meaningful Hotkeys

Use standard keyboard shortcuts when possible:
// Standard shortcuts
Bold:      'mod+b'
Italic:    'mod+i'
Underline: 'mod+u'

// Custom shortcuts should be intuitive
Superscript: 'mod+.'
Subscript:   'mod+,'

4. Handle Mark Conflicts

Some marks shouldn’t coexist (e.g., superscript and subscript):
const Superscript = createYooptaMark({
  type: 'superscript',
  render: ({ children, leaf }) => {
    // Don't render if subscript is active
    if (leaf.subscript) return children;
    return <sup>{children}</sup>;
  },
});

Next Steps

Elements

Learn about element structures

Plugins

Build custom plugins with marks

Themes

Style marks with themes

Editor Instance

Back to editor API reference

Build docs developers (and LLMs) love