All questions

Rich Text Editor

Premium

Rich Text Editor

Build a small rich-text editor: a contentEditable region plus a Bold / Italic / Underline / Bullet-list toolbar. Clicking a button formats the current selection, and each button lights up when the caret sits inside text that already has that format. This is the classic document.execCommand editor — the same API that powered compose boxes like Gmail's for years.

What you'll build

The starter App.tsx renders the toolbar and a contentEditable editor with seed text, but the buttons do nothing. Wire it up:

  1. Apply formatting. Each button's onClick runs document.execCommand(cmd)'bold', 'italic', 'underline', or 'insertUnorderedList' — which mutates the editable DOM for the current selection.
  2. Track active state. Keep an active object in useState ({ bold, italic, underline, list }, all false). Add the active class to a button when its command is currently on.
  3. Sync on selection change. Recompute active from document.queryCommandState(cmd) on the editor's onKeyUp / onMouseUp and on a document selectionchange listener (added in useEffect, cleaned up on unmount).
  4. Keep the selection. Add onMouseDown={(e) => e.preventDefault()} to every button so clicking it doesn't blur the editor and drop the selection before execCommand runs.

Examples

  • Select the word editor and click B: it becomes bold, and the B button turns green. Move the caret out of the bold text and B goes back to inactive.
  • With the caret inside already-bold text, B shows active even though you never clicked it this session — queryCommandState reports the caret's real format.
  • Click the bullet button with the caret on a line: document.execCommand('insertUnorderedList') wraps it in a ul / li.

Notes

  • execCommand is deprecated but still ships in every browser; it's the pragmatic way to build a toolbar without a full document model. The solution covers the modern alternative.
  • The DOM owns the text, not React. execCommand writes straight into the contentEditable element — your active state only reads back what's there; it never controls the content.
  • Styling (dark theme, toolbar, active pill) is already in styles.css; focus on the behavior.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium