formatBold

How formatBold inputType applies bold formatting and varies across browsers.

Overview

The formatBold inputType is triggered when the user presses Ctrl/Cmd + B. The browser wraps selected text in a bold element, but the exact element and behavior varies.

Basic Behavior

Scenario 1: Text selected within paragraph

Before (Text selected)

HTML:
<p>Hello world text</p>
Visual: Hello world text

After Cmd/Ctrl + B (Chrome/Edge)

HTML:
<p>Hello <strong>world</strong> text</p>
Uses <strong> element

After Cmd/Ctrl + B (Firefox)

HTML:
<p>Hello <b>world</b> text</p>
Uses <b> element

After Cmd/Ctrl + B (Safari)

HTML:
<p>Hello <strong>world</strong> text</p>
Uses <strong> element (similar to Chrome)

Toggle Behavior

Scenario 2: Already bold text selected

Before (Bold text selected)

HTML:
<p>Hello <strong>world</strong> text</p>

After Cmd/Ctrl + B (Removes bold)

HTML:
<p>Hello world text</p>
Removes <strong> wrapper

Scenario 3: Partial bold selection

Before (Selection spans bold and normal)

HTML:
<p>He<strong>llo wo</strong>rld</p>
Selection: "ello world" (partially bold)

After Cmd/Ctrl + B (Normalizes structure)

HTML:
<p>He<strong>llo wo</strong><strong>rld</strong></p>
Splits and wraps all selected text in <strong>

Collapsed Selection (Cursor Position)

⚠️ Critical Edge Case

When the selection is collapsed (cursor position, no text selected), pressing Cmd/Ctrl + B behaves differently:

  • beforeinput may or may not fire
  • input typically does not fire
  • Browser toggles "bold state" for the next character to be typed

Scenario 4: Collapsed cursor in normal text

Before (Cursor at position 5)

HTML:
<p>Hello|world</p>

After Cmd/Ctrl + B (Toggles bold state)

HTML:
<p>Hello|world</p>
No DOM change yet - browser sets "bold state"

After typing 'x' (Bold applied)

HTML:
<p>Hello<strong>x</strong>world</p>
Next typed character is wrapped in <strong>

IME Composition + formatBold

⚠️ Critical Issue

During IME composition, pressing Cmd/Ctrl + B may not work as expected:

  • macOS + Korean IME: formatBold does not fire; insertCompositionText fires instead. Formatting is completely ignored.
  • Other platforms: beforeinput may not fire; only input fires (cannot prevent). Formatting may be applied unexpectedly.

Impact: Formatting shortcuts are unreliable during IME composition, making it difficult to intercept and customize formatting.

Workaround

To handle IME composition issues:

  • Listen for insertCompositionText events during composition
  • Track formatting intent separately when composition is active
  • Apply formatting after composition ends if formatting was requested
  • Use keydown events to detect shortcuts during composition (may interfere with IME)

Browser-Specific Differences

Element Used for Bold

Chrome/Edge

  • Uses <strong> element
  • More semantic HTML

Firefox

  • Uses <b> element
  • Less semantic, but visually equivalent

Safari

  • Uses <strong> element
  • Similar to Chrome

Editor Internal Model & DOM Synchronization

⚠️ Exception: preventDefault() and DOM Manipulation

When editors use preventDefault() and manipulate DOM directly:

  • Browser's default formatting behavior is prevented
  • Editor applies formatting to its internal model, then re-renders DOM
  • Browser's undo stack may not include the formatting operation (because default was prevented)
  • If editor re-renders entire DOM from model, browser may lose track of the specific formatting change

Impact: Undo operations may not work as expected, or formatting may be lost during undo/redo cycles if the editor's internal model and DOM become out of sync.

Editor-Specific Handling

Different editor frameworks handle bold formatting differently. Here's how major editors implement formatBold:

Slate.js

Bold Formatting

Slate uses Transforms.setNodes() to toggle formatting marks on text nodes:

    import { Editor, Transforms, Text } from 'slate';

function isBoldActive(editor: Editor) {
  const marks = Editor.marks(editor);
  return marks?.bold === true;
}

function handleFormatBold(editor: Editor) {
  const isActive = isBoldActive(editor);
  Transforms.setNodes(
    editor,
    { bold: !isActive },
    { match: n => Text.isText(n), split: true }
  );
}

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatBold') {
    e.preventDefault();
    handleFormatBold(editor);
  }
});
  
  • Mark-based: Formatting is stored as properties on text nodes (e.g., { bold: true }).
  • Transforms API: Uses Transforms.setNodes() to apply/remove formatting.
  • Serialization: Marks are converted to HTML elements (e.g., <strong>) during rendering.
ProseMirror

Bold Formatting

ProseMirror uses mark commands and keymaps to handle formatting:

    import { toggleMark } from 'prosemirror-commands';
import { schema } from './schema';

function handleFormatBold(state, dispatch) {
  return toggleMark(schema.marks.strong)(state, dispatch);
}

// In keymap
{
  'Mod-b': (state, dispatch) => {
    return handleFormatBold(state, dispatch);
  }
}

// Or intercept beforeinput
view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatBold') {
    e.preventDefault();
    const { state, dispatch } = view;
    if (handleFormatBold(state, dispatch)) {
      // Handled
    }
  }
});
  
  • Mark system: Formatting is represented as marks in the document schema (e.g., schema.marks.strong).
  • Commands: Uses toggleMark() command to apply/remove marks.
  • Keymap: Keyboard shortcuts are defined in keymap plugins (e.g., Mod-b for bold).
Draft.js

Bold Formatting

Draft.js uses inline styles and RichUtils to handle formatting:

    import { EditorState, RichUtils } from 'draft-js';

function handleFormatBold(editorState) {
  return RichUtils.toggleInlineStyle(editorState, 'BOLD');
}

// In key binding function
function myKeyBindingFn(e) {
  if (e.keyCode === 66 && (e.ctrlKey || e.metaKey)) {
    return 'bold';
  }
  return getDefaultKeyBinding(e);
}

// In key command handler
function handleKeyCommand(command) {
  if (command === 'bold') {
    const newState = handleFormatBold(editorState);
    setEditorState(newState);
    return 'handled';
  }
  return 'not-handled';
}
  
  • Inline styles: Formatting is stored as inline style strings (e.g., 'BOLD') in the ContentState.
  • RichUtils: Uses RichUtils.toggleInlineStyle() to toggle formatting.
  • Key bindings: Custom key binding function maps keyboard shortcuts to commands.

Related resources