Markdown Editor
A real textarea, a toolbar that edits the selection through setRangeText — so the browser undo stack survives — and the Markdown component as its preview.
src/components/ui/markdown-editor.tsxAbout
From the component's own source, where it was written.
A markdown editor: a real textarea, a toolbar that edits the selection, and the Markdown renderer as its preview.
**The input is a plain <textarea>.** Rich contenteditable editors have to reimplement undo, IME composition, spellcheck, autocorrect, mobile keyboards and every accessibility affordance a textarea gets from the platform — and they get the undo stack wrong first. Here the toolbar goes through setRangeText, which means **the browser's own undo history stays intact**: ctrl-Z after clicking Bold does what you expect, which is not true of an editor that rewrites value from React state.
**Preview is the same component that renders the document elsewhere.** An editor whose preview is a different renderer teaches you a formatting model your readers will not see, so what you check in the split view is exactly what Markdown produces — including where it stops.
Wrapping is selection-aware in the way people expect: with text selected the markers go around it, with nothing selected they are inserted and the caret lands **between** them, and applying the same marker to an already-wrapped selection unwraps it.
Install
npx astralyx-ui add markdown-editor
Usage
import { MarkdownEditor } from '@/components/ui/markdown-editor'
<MarkdownEditor value={text} onChange={setText} defaultMode="split" />Composer
Change the props and the source below follows.
Pull request
Adds a QR encoder so TwoFactorSetup can fill its own qr slot.
- Byte mode, versions 1-10
- All eight masks scored, per the spec
- Verified against an independent decoder
Nothing leaves the page.
<MarkdownEditor
value={text}
onChange={setText}
defaultMode="split"
/>Examples
Write and preview together
<MarkdownEditor value={text} onChange={setText} defaultMode="split" />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
MarkdownEditor
Extends Omit<ComponentProps<'div'>, 'onChange' | 'defaultValue'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | The markdown source. Controlled or uncontrolled. |
| defaultValue | string | '' | — |
| onChange | (value: string) => void | — | The markdown source. Controlled or uncontrolled. |
| placeholder | string | 'Write markdown…' | — |
| rows | number | 12 | Starting rows. Also the minimum height when autoResize is on. |
| autoResize | boolean | true | Grow the textarea with its content, up to maxRows. |
| maxRows | number | 30 | Ceiling for autoResize. Past this the pane scrolls. |
| syncScroll | boolean | true | Keep the two panes aligned while scrolling in split mode. |
| defaultMode | Mode | 'write' | — |
| modes | Mode[] | ['write', 'preview', 'split'] | Drop 'split' on narrow screens by passing your own list. |
| toolbar | boolean | true | — |
| actions | ReactNode | — | Trailing slot in the toolbar — a save button, a character count. |
| label | string | 'Markdown editor' | — |
| disabled | boolean | — | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| a plain textarea | not contenteditable | Rich editors reimplement undo, IME composition, spellcheck, autocorrect, mobile keyboards and every accessibility affordance a textarea gets free — and get the undo stack wrong first. |
| setRangeText | keeps undo | The toolbar edits through the element, so ctrl-Z after clicking Bold does what you expect. Rewriting value from React state does not. |
| preview | the same renderer | An editor whose preview differs from the reader’s view teaches a formatting model your readers will not see — including where [[markdown]] stops. |
| wrapping | selection-aware | With text selected the markers go around it; with nothing selected the caret lands between them; applying the same marker again unwraps it. |
| shortcuts | B / I / K | Tab is deliberately left alone — trapping it breaks keyboard navigation out of the field. |