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)
After Cmd/Ctrl + B (Chrome/Edge)
After Cmd/Ctrl + B (Firefox)
After Cmd/Ctrl + B (Safari)
Toggle Behavior
Scenario 2: Already bold text selected
Before (Bold text selected)
After Cmd/Ctrl + B (Removes bold)
Scenario 3: Partial bold selection
Before (Selection spans bold and normal)
After Cmd/Ctrl + B (Normalizes structure)
Collapsed Selection (Cursor Position)
⚠️ Critical Edge Case
When the selection is collapsed (cursor position, no text selected), pressing Cmd/Ctrl + B behaves differently:
beforeinputmay or may not fireinputtypically 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)
After Cmd/Ctrl + B (Toggles bold state)
After typing 'x' (Bold applied)
IME Composition + formatBold
⚠️ Critical Issue
During IME composition, pressing Cmd/Ctrl + B may not work as expected:
- macOS + Korean IME:
formatBolddoes not fire;insertCompositionTextfires instead. Formatting is completely ignored. - Other platforms:
beforeinputmay not fire; onlyinputfires (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
insertCompositionTextevents during composition - Track formatting intent separately when composition is active
- Apply formatting after composition ends if formatting was requested
- Use
keydownevents 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:
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.
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-bfor bold).
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.