Markdown
Render markdown with MarkdownView and edit it with TextEdit in one typography — every element, slash menu, bubble menu, shortcuts, input rules.
Last updated
Markdown in VegaStack has two surfaces and one typography. MarkdownView
renders a markdown string; TextEdit with format="markdown" edits
one. Both wear the shared prose recipe from @vegastack/design (proseClassName), and TextEdit
resets ProseMirror's defaults, so a document sits at the same position whether it is being read or
edited. The recipe is the app's body text — text-sm, the body family and foreground ink for
paragraphs, lists, list markers, marks and quotes, headings on the app's type scale — so neither
surface has a separate "prose" look. Only inline code and code blocks are mono.
TextEdit is Notion-style: no border, no ground, no focus ring and no view mode. It rests looking
exactly like MarkdownView; click anywhere and type. Leaving the editor calls onCommit with the
new markdown when it changed; Esc reverts.
import { MarkdownView } from "@/components/ui/markdown-view";
import { TextEdit } from "@/components/ui/text-edit";
// Editable: the editor is the view.
<TextEdit
format="markdown"
defaultValue={summary}
onCommit={save}
aria-label="Summary"
/>;
// Read-only elsewhere:
<MarkdownView>{summary}</MarkdownView>;Every element, view and edit
The same document rendered by MarkdownView and edited by a TextEdit, stacked. Headings h1–h6, paragraphs, bold, italic, strike, inline code, fenced
code blocks, links, bullet, ordered and task lists (nested), blockquotes, rules, tables, images and
hard breaks. Edit the lower copy: the upper one follows.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
A paragraph with bold, italic, strikethrough, inline code and a link (opens in new tab).
A hard line break follows,
then https://example.com (opens in new tab) autolinks in view mode.
- Bullet item
- Another item
- Nested item
- First
- Second
- Nested ordered
- Done task
- Open task
- Nested task
A blockquote carries a quoted thought.
const answer = 42;
| Name | Role |
|---|---|
| Ada | Engineer |
| Grace | Admiral |
| Element | Markdown | Notes |
|---|---|---|
| Headings | # … ###### | MarkdownView headingOffset moves them down under a page heading. |
| Bold / italic | **b** / _i_ | |
| Strikethrough | ~~s~~ | Needs the strike action to edit. |
| Inline code | `c` | A muted chip. |
| Code block | ```ts | The CodeBlock surface (language and copy) in both modes. |
| Link | [t](url) | View mode autolinks bare URLs; external links open a new tab with rel="noopener". |
| Lists | - / 1. | Nest with two (bullet) or three (ordered) spaces. |
| Task list | - [ ] / - [x] | The system Checkbox is the marker; it toggles in edit mode. |
| Blockquote | > | |
| Rule | --- | |
| Table | GFM pipes | Scrolls inside its own box. |
| Image |  | MarkdownView blocks remote origins unless listed in allowedImageOrigins. |
| Hard break | trailing \ |
Slash menu
There is no toolbar. Type / at the start of a line or after a space to open the block menu, then type to filter it. ↑ ↓ move, Enter inserts and
Esc closes it with the text left as typed.
| Command | Inserts | Markdown shortcut |
|---|---|---|
| Text | a plain paragraph | |
| Heading 1–3 | a heading | # ## ### |
| Bullet list | a bullet list | - |
| Numbered list | an ordered list | 1. |
| Checklist | a task list | [ ] |
| Quote | a blockquote | > |
| Code block | a fenced code block | ``` |
| Divider | a horizontal rule | --- |
| Link | a link, through the link input | Mod K |
slashCommands limits and orders the menu, for example a comment composer that offers only lists,
code and links. [] turns the menu off. It limits the menu only: markdown typed or pasted for any
element still works, so every document round-trips.
A smaller set
Bubble menu
Selecting text floats a small menu with bold, italic, strikethrough, inline code and link. The link
button, and Mod K, turn the menu into an inline URL input: Enter
applies it, Esc closes it, and Remove unlinks. Pasting a URL over a selection links
it directly. Links open in a new tab with rel="noopener noreferrer".
In-place editing
There is no mode to switch. The editor is transparent and borderless, so it takes the surface it
sits on and its text lands exactly where MarkdownView would put it. Hover and focus show a faint
background tint, never a border or ring, and an empty focused editor hints "Type / for commands".
There are no Save or Cancel buttons.
| Event | Result |
|---|---|
| Focus leaves the editor | onCommit(value) — only when the document differs from when focus came. |
| Esc | Revert to the document focus found, blur, then onRevert(). |
| Mod Enter | onSubmit(value) when set; otherwise commit and blur. |
autosave idle gap | onCommit(value) without leaving. |
The page going hidden and the editor unmounting mid-edit commit through the same path, so a value is
never committed twice. A new value that arrives while the editor is focused waits for blur, so the
caret never jumps.
Autosave
autosave also calls onCommit after an idle gap (true is 1000ms, or pass milliseconds). It is off by
default.
Shortcuts
Mod is ⌘ on Apple platforms and Ctrl
elsewhere.
| Keys | Action |
|---|---|
| Mod B | Bold |
| Mod I | Italic |
| Mod K | Add or edit the link |
| Mod E | Inline code |
| Mod Shift S | Strikethrough |
| Mod Alt 2 / 3 | Heading 2 / 3 |
| Mod Shift 7 / 8 / 9 | Ordered / bullet / task list |
| Mod Shift B | Blockquote |
| Mod Alt C | Code block |
| Mod Z / Mod Shift Z | Undo / redo |
| Mod Enter | Submit (onSubmit) or commit |
| Esc | Revert (onRevert) |
| Tab / Shift Tab | Indent / outdent a list item |
Input rules
Type markdown and it becomes formatting as you go. Pasted plain-text markdown is parsed the same way.
| Type | Becomes |
|---|---|
# … ###### | Heading |
- or * | Bullet list |
1. | Ordered list |
[ ] or [x] | Task item |
> | Blockquote |
``` | Code block |
--- | Divider |
**text** | Bold |
_text_ | Italic |
~~text~~ | |
`text` | Inline code |
Every rule works in every editor: slashCommands limits the menu, never the schema.