1 slot open this monthBy the makers of shadcnregistryWe built shadcnregistry. Now we build your product.Book a callCheck availability
1 slot open this monthAI features?We build AI features into your product.Book a callCheck availability
1 slot open this month🚨 Slop police🚨 Slop police: we clean up vibe-coded apps.Report itReport your app
shadcnregistry
shadcnregistry
RegistryHealthInspect
Terms of ServicePrivacy Policy
  1. Registries
  2. pulld
  3. Markdown Toolbar

Markdown Toolbar

ui

Puts a row of formatting buttons over a plain `<textarea>` — bold, italic, inline code and fenced blocks, links, bulleted and numbered lists, quotes — and writes the markers into the text without taking away the browser's own undo. Reach for it above any box somebody types prose into: a comment field, an issue or bug-report form, a pull-request description, a review box, a chat composer, a changelog or release-notes field, a README editor in an admin page, a support-ticket reply, a CMS body field, a notes or docs panel. Common asks it answers: "markdown toolbar react", "markdown editor toolbar shadcn", "textarea formatting buttons", "bold button for textarea", "wrap selected text in asterisks react", "insert markdown at cursor react", "textarea selectionStart selectionEnd react", "setRangeText react", "document.execCommand insertText react", "preserve undo after programmatic textarea edit", "cmd+z stops working after setRangeText", "textarea undo history broken", "markdown shortcuts cmd+b textarea", "keyboard shortcut bold textarea react", "toggle markdown bold off", "unwrap markdown markers", "prefix every selected line react", "bullet list button textarea", "renumber ordered list markdown", "blockquote button react", "insert link markdown caret in url", "toolbar button steals focus from textarea", "button mousedown preventDefault keep selection", "markdown ツールバー react", "textarea 選択範囲 装飾", "太字ボタン react", "undo が効かなくなる textarea", "markdown editor react no dependencies", "lightweight markdown editor react", "simplemde alternative", "easymde alternative", "@uiw/react-md-editor alternative", "react-markdown-editor-lite alternative", "toast ui editor alternative", "tiptap alternative for markdown", "lexical vs markdown textarea", "rich text editor without contenteditable", "github style comment box react", "add formatting buttons to a comment box", "shadcn textarea with toolbar", "markdown toolbar for react-hook-form textarea", "markdown composer next.js", "軽量 マークダウンエディタ react", "コメント欄 装飾ボタン", "textarea に太字ボタンを付けたい". **The undo stack is the whole engineering problem, and the obvious modern call is the one that breaks it.** `textarea.setRangeText` writes the text and records no undo entry: measured in Chrome 152, a `setRangeText` edit followed by `execCommand("undo")` came back exactly as the edit had left it — and that `undo` *returned `true`*, so the failure is invisible from the return value. The same edit through the deprecated `document.execCommand("insertText")` was reverted, in every focus arrangement tried, with two consecutive writes landing as two separate undo steps and a multi-line rewrite landing as one. So `insertText` is tried first behind a `queryCommandSupported` check and `setRangeText` is the fallback for the day it is removed, which is an uncomfortable thing to ship and still the only arrangement in which Cmd+Z after pressing Bold does what everybody expects. The fallback dispatches its own `input` event, because a controlled React textarea learns about a change from exactly that event and `setRangeText` fires none — without it the edit lands and the next render snaps it back. One edit is one range replacement, which is what keeps it to one undo step. **The focus guard is real and the usual reason given for it is not.** Pressing a toolbar button does not cost you the selection: measured through a real click on an unguarded button, `selectionStart`/`selectionEnd` still read 6 and 11 over the same word at mousedown, at click and after the write, because a textarea's selection belongs to the field and survives it losing focus. What the `mousedown` guard removes is a focus round trip — the unguarded click fired `blur` once and `focus` twice on the textarea, the guarded one fired neither — and those events are load-bearing in real forms, where save-the-draft and run-validation and close-the-composer all hang off blur. **Line markers and surrounding markers are different operations, and the mixed case is where implementations feel broken.** Bold surrounds the selection; a bullet goes at the head of every line the selection touches. Select four lines of which one is already a bullet, and a per-line toggle gives back three bullets and one plain line — so the marker comes off only when every line already carries it, and otherwise it is brought to all of them: one press to normalise, a second to clear. Blank lines are left alone and excluded from that vote. Numbers are rewritten to their position rather than repeated, so a block numbered 3, 7, 7 comes back 1, 2, 3. A selection ending at column 0 — one line of visible highlight, dragged down and released at the next line's left edge — does not reach the line it merely touched. **Toggling off is tested in both of the arrangements markers can be in, which is what stops it doubling them.** After a wrap the highlight stays on the words the person highlighted rather than on the markers, so a second press sees the markers outside the selection, while somebody who dragged across `**bravo**` by hand has them inside it; both are "this is already bold". The run of markers on each side is tokenised and the pairing has to balance, so the bold button finds its own markers through a layer of italics — pressing bold on `**_bravo_**` gives `_bravo_` and not `**_**bravo**_**`, which is what a check that only looks at the characters touching the selection produces. That requirement is also what stops it firing on `**bold**text`, where `**` touches the selection on the left and nothing closes it on the right. An empty selection inserts the pair and parks the caret between them; for a link the caret goes to whichever slot is still empty, so a selected URL becomes the target with the caret waiting in the label, and no placeholder word is left behind to be mistaken for a real one. One tab stop for the whole row with the arrow keys moving inside it, Home and End to the ends, and the arrows following the text on an RTL page; `role="toolbar"` with a label, every control a real `type="button"` so a toolbar inside a form cannot submit it, `aria-keyshortcuts` on the buttons that have a chord, Mod+B / Mod+I / Mod+E / Mod+K bound on the field where the hands are and accepting either modifier, and decorative icons `aria-hidden`. Theme tokens throughout with no hardcoded colours, one lucide icon set, and nothing else added. **It is not a markdown editor.** It writes markers into plain text and never parses: no preview, no syntax highlighting, no AST, and the toggles read the characters around the selection rather than a document model, so nesting a real parser would resolve is left to the person. It renders only the toolbar and the textarea stays yours — your rows, your placeholder, your validation, this registry's autosize-textarea if you want one. Official shadcn/ui has nothing here and the measurement is not close: fetching every entry in its registry today — 65 listed, 62 fetchable, code-block and questionnaire and trace 404 on both style tracks — and searching all 223,287 bytes of it, the strings execCommand, setRangeText, selectionStart, selectionEnd, insertText, markdown, toolbar, italic and blockquote are every one of them a zero hit; its `textarea` is a six-occurrence style wrapper and all six hits for "bold" are `font-semibold` in a title class. Official never touches a field's selection. Within pulld it sits beside selection-toolbar, which floats over text somebody is reading, and mention-input, which completes a word somebody is typing; this is the only component here that rewrites the selection in a field. Against the editor libraries rather than against official: tiptap, Lexical, SimpleMDE and the `@uiw` editors replace the field with a contenteditable surface and own the document model, which buys WYSIWYG and costs the native textarea — its undo stack, its spellcheck, its mobile keyboard behaviour, form submission, and a bundle measured in tens of kilobytes. This keeps the `<textarea>` and only writes into it, so what the user types is still markdown and what the form posts is still a string. Reach for a real editor when you need rendered output while typing; reach for this when the field is already a textarea and the text is already markdown.

pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/markdown-toolbar.json"
Premium blocks for shadcn/uiShadcnblocks.com · AffiliateExplore
  • lucide-react
    components/ui/markdown-toolbar.tsx