⚠️ Deprecated - Do Not Use
The document.execCommand() API has been deprecated and should not be used in new code. It may stop working in future browser versions, and its behavior is inconsistent across browsers.
Always use modern alternatives: Selection API, Range API, and the beforeinput event with inputType instead.
execCommand to inputType Mapping
Many execCommand operations are now available as inputType values in the beforeinput and input events. This allows you to intercept and handle formatting operations before they're applied to the DOM.
Common execCommand to inputType Mappings
| execCommand | inputType | Browser Behavior | Keyboard Shortcut |
|---|---|---|---|
bold | formatBold | Wraps selected text in <strong> or <b> element. If already bold, removes formatting. | Ctrl/Cmd + B |
italic | formatItalic | Wraps selected text in <em> or <i> element. Toggles italic formatting. | Ctrl/Cmd + I |
underline | formatUnderline | Wraps selected text in <u> element. Toggles underline formatting. | Ctrl/Cmd + U |
strikeThrough | formatStrikeThrough | Wraps selected text in <s> or <strike> element. Toggles strikethrough. | No standard shortcut |
createLink | formatCreateLink | Wraps selected text in <a href="..."> element. Prompts for URL if not provided. | Ctrl/Cmd + K |
insertUnorderedList | insertUnorderedList | Wraps selected paragraphs in <ul> with <li> items. Converts existing list if present. | No standard shortcut |
insertOrderedList | insertOrderedList | Wraps selected paragraphs in <ol> with <li> items. Converts existing list if present. | No standard shortcut |
formatBlock | formatBlock | Wraps selected content in block element (e.g., <h1>, <p>, <div>). Replaces existing block if selection spans entire block. | No standard shortcut |
insertHTML | insertHTML | Inserts HTML string at selection. Replaces selection if text is selected. Parses HTML and inserts as DOM nodes. | N/A (programmatic only) |
delete | deleteContent | Deletes selected content. If collapsed, deletes character after cursor (forward delete). | Delete or Backspace |
forwardDelete | deleteContentForward | Deletes character after cursor (forward delete). Equivalent to Delete key. | Delete |
undo | historyUndo | Undoes last operation. Browser maintains undo stack for contenteditable regions. | Ctrl/Cmd + Z |
redo | historyRedo | Redoes last undone operation. Browser maintains redo stack. | Ctrl/Cmd + Shift + Z |
selectAll | selectAll | Selects all content in the contenteditable element. | Ctrl/Cmd + A |
copy | N/A (Clipboard API) | Copies selected content to clipboard. Use navigator.clipboard.writeText() or Clipboard API instead. | Ctrl/Cmd + C |
cut | N/A (Clipboard API) | Cuts selected content to clipboard. Use navigator.clipboard.writeText() and range.deleteContents() instead. | Ctrl/Cmd + X |
paste | insertFromPaste | Pastes content from clipboard. Use navigator.clipboard.readText() or Clipboard API, then insert at selection. | Ctrl/Cmd + V |
Note: Not all execCommand operations have corresponding inputType values. Some operations (like copy, cut) should use the Clipboard API instead.
Using beforeinput with inputType
The beforeinput event fires before formatting operations are applied. You can intercept these events, prevent the default behavior, and implement your own formatting logic using the Selection API.
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'formatBold') {
e.preventDefault();
// Apply bold formatting using Selection API
applyBold();
}
});Editor-Specific Implementations
Different editor frameworks handle formatting operations differently. Here's how major editors implement bold formatting as an example:
Bold Formatting
Slate uses Transforms.setNodes() to toggle formatting marks on text nodes:
import { Editor, Transforms } from 'slate';
import { ReactEditor } from 'slate-react';
function handleFormatBold(editor: Editor) {
const isActive = isBoldActive(editor);
Transforms.setNodes(
editor,
{ bold: !isActive },
{ match: n => Text.isText(n), split: true }
);
}
// In beforeinput handler
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);
}
}
- 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 { 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.
Using the Selection API
Instead of execCommand('bold'), you can wrap the selected text in a <strong> element using the Selection and Range APIs.
function applyBold() {
const selection = window.getSelection();
if (selection.rangeCount === 0) return;
const range = selection.getRangeAt(0);
const strong = document.createElement('strong');
try {
strong.appendChild(range.extractContents());
range.insertNode(strong);
selection.removeAllRanges();
selection.addRange(range);
} catch (e) {
console.error('Failed to apply bold:', e);
}
}Platform-Specific Issues & Edge Cases
The behavior of beforeinput events and inputType values can vary significantly depending on browser, device, OS, and keyboard type. These variations can cause unexpected behavior when implementing execCommand alternatives.
Browser-Specific Issues
⚠️ Safari: beforeinput Limitations
Safari: While Safari supports beforeinput events, there may be differences in behavior compared to Chrome/Edge:
- Some
inputTypevalues may not be supported or may behave differently - Event timing may differ, especially during IME composition
- Formatting operations may fire in different order or with different properties
- Older Safari versions may have limited or no support
Best Practice: Always test beforeinput behavior in Safari and provide fallbacks for unsupported inputType values.
⚠️ Chrome/Edge: Event Order Variations
Chrome/Edge: The order of beforeinput and compositionupdate events may differ from other browsers, especially during IME composition.
- In some cases,
compositionupdatemay fire beforebeforeinput - This can cause timing issues when handling formatting during composition
⚠️ Firefox: Different inputType Values
Firefox: May use different inputType values or fire events in different orders than Chrome/Edge.
- Some formatting operations may not have corresponding
inputTypevalues - Event timing may differ, especially for complex operations
OS & Keyboard-Specific Issues
⚠️ macOS + Korean IME: Formatting Events Don't Fire
macOS + Korean IME (2벌식, 3벌식, etc.): During IME composition with a collapsed cursor, pressing formatting shortcuts (e.g., Cmd + B, Cmd + I) may cause unexpected behavior:
formatBold,formatItalic, etc. do not fire- Instead,
insertCompositionTextmay fire - Formatting is completely ignored during composition
- User must commit composition first, then apply formatting
Impact: Users cannot apply formatting while composing Korean text, breaking expected workflow.
⚠️ Windows: IME Behavior Varies
Windows: IME behavior varies by Windows version and IME provider (Microsoft IME, Google IME, etc.).
- Different IME engines may fire events at different times
- Formatting shortcuts during composition may behave differently
- Event timing can vary between Windows 10 and Windows 11
⚠️ Keyboard Layout Differences
Different keyboard layouts: Korean IME layouts (2벌식, 3벌식, 390 자판) may affect when formatting events fire.
- Composition event timing differs between layouts
- Formatting shortcuts may be intercepted differently
- Event patterns can vary even for the same text input
Device-Specific Issues
⚠️ Mobile Keyboards: Text Prediction Interference
Mobile devices (Android/iOS): Virtual keyboards with text prediction features can interfere with formatting operations:
- Android Samsung Keyboard: Text prediction may cause
beforeinputto not fire before suggestion insertion - iOS QuickType: May use different
inputTypevalues (insertReplacementTextvsinsertFromPredictiveText) - Multiple
inputevents may fire for a single suggestion - Formatting shortcuts may not work as expected on mobile keyboards
- Physical keyboard attachments may behave differently than virtual keyboards
⚠️ Tablet vs Desktop
Tablets: Hybrid behavior between desktop and mobile:
- External keyboard may behave like desktop
- On-screen keyboard may behave like mobile
- Event patterns can switch dynamically
Selection State Issues
⚠️ Collapsed Selection: Events May Not Fire
Collapsed selection (cursor only): When the selection is collapsed (no text selected), formatting operations may behave unexpectedly:
beforeinputmay or may not fire (browser-dependent)inputtypically does not fire- Browser may toggle "formatting state" for next character instead
- This makes it difficult to detect when formatting is applied
⚠️ IME Composition + Collapsed Selection
Most unpredictable case: Collapsed selection during IME composition:
- Formatting events may fire in unexpected order or not at all
- Browser behavior varies significantly
- macOS Korean IME: Formatting shortcuts completely ignored
- Windows IME: May or may not fire formatting events
Best Practices for Handling Platform Differences
- Detect browser capabilities: Check if
beforeinputis supported before relying on it - Handle both events: Listen to both
beforeinputandinputto catch all cases - Check composition state: Always check
isComposingflag before handling formatting - Test across platforms: Test with different browsers, OS, keyboards, and devices
- Provide fallbacks: Use
keydownevents as fallback for Safari - Monitor DOM changes: Compare DOM state before/after events to detect what actually changed
- Handle edge cases: Account for collapsed selections, IME composition, and mobile keyboards
Challenges
Complexity: The Selection API approach is more complex than execCommand and requires careful handling of edge cases (empty selections, collapsed ranges, cross-element selections, etc.).
Browser differences: Selection and Range behavior varies across browsers, especially when dealing with complex DOM structures, IME composition, or nested formatting.
No standard replacement: There is no single, standardized replacement for execCommand. Each operation must be implemented separately using the Selection API, and behavior may differ from execCommand's original implementation.