formatUnderline

How formatUnderline inputType applies underline formatting and varies across browsers.

Overview

The formatUnderline inputType is triggered when the user presses Ctrl/Cmd + U. The browser wraps selected text in a <u> element.

Basic Behavior

Scenario: Text selected within paragraph

Before (Text selected)

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

After Cmd/Ctrl + U

HTML:
<p>Hello <u>world</u> text</p>
Uses <u> element (consistent across browsers)

Collapsed Selection

⚠️ Critical Edge Case

When the selection is collapsed, pressing Cmd/Ctrl + U toggles underline state for the next character. input event typically does not fire.

IME Composition + formatUnderline

⚠️ Critical Issue

During IME composition, pressing Cmd/Ctrl + U may not work as expected. On macOS with Korean IME, formatUnderline does not fire; insertCompositionText may fire instead.

See formatBold for detailed workarounds.

Editor-Specific Handling

Different editor frameworks handle underline formatting similarly to other text formatting. Here's how major editors implement formatUnderline:

Slate.js

Underline Formatting

Slate uses Transforms.setNodes() to toggle underline marks:

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

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

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

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatUnderline') {
    e.preventDefault();
    handleFormatUnderline(editor);
  }
});
  
  • Mark-based: Formatting stored as { underline: true } on text nodes.
  • Same pattern: Uses identical Transforms API as other formatting marks.
ProseMirror

Underline Formatting

ProseMirror uses toggleMark with underline mark:

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

function handleFormatUnderline(state, dispatch) {
  return toggleMark(schema.marks.underline)(state, dispatch);
}

view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatUnderline') {
    e.preventDefault();
    const { state, dispatch } = view;
    if (handleFormatUnderline(state, dispatch)) {
      // Handled
    }
  }
});
  
  • Mark system: Uses schema.marks.underline mark type.
  • Same command: Uses toggleMark() like other formatting.
Draft.js

Underline Formatting

Draft.js uses RichUtils.toggleInlineStyle() with UNDERLINE:

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

function handleFormatUnderline(editorState) {
  return RichUtils.toggleInlineStyle(editorState, 'UNDERLINE');
}

function myKeyBindingFn(e) {
  if (e.keyCode === 85 && (e.ctrlKey || e.metaKey)) {
    return 'underline';
  }
  return getDefaultKeyBinding(e);
}

function handleKeyCommand(command) {
  if (command === 'underline') {
    const newState = handleFormatUnderline(editorState);
    setEditorState(newState);
    return 'handled';
  }
  return 'not-handled';
}
  
  • Inline style: Uses 'UNDERLINE' style string.
  • Same utility: Uses RichUtils.toggleInlineStyle like other formatting.

Related resources