Toolbar
In edit-md mode a toolbar offers inline formatting (bold, italic,
strikethrough, inline code) plus Insert table and Insert drawing.
Hide it with toolbar={false}.
Extending the toolbar
Pass a function as toolbar. It receives the default button groups
(format, insert, history) and returns the toolbar to render. Use
MarkdownEditor.ToolbarButton for your own buttons so they match the
built-ins and keep the editor selection when clicked.
<MarkdownEditor
value={value}
onChange={setValue}
toolbar={(items) => (
<MarkdownEditor.Toolbar>
{items.format}
<MarkdownEditor.ToolbarDivider />
{items.insert}
<MarkdownEditor.ToolbarDivider />
{items.history}
<MarkdownEditor.ToolbarButton label="Save" onClick={save}>
💾
</MarkdownEditor.ToolbarButton>
</MarkdownEditor.Toolbar>
)}
/>
MarkdownEditor.Toolbar hides itself in view / edit-raw / readOnly.
The undo / redo buttons (items.history) are only in the default layout
through this function form; the plain toolbar={true} layout is format +
insert.
This example reorders the groups (history first) and adds a copy button:
Placing your own toolbar (compound components)
When the toolbar must live somewhere else in your layout (an app bar, a
panel header), compose the editor from its parts. Everything under
MarkdownEditor.Root shares one editor instance, so the toolbar can sit
anywhere in that subtree.
<MarkdownEditor.Root value={value} onChange={setValue}>
<header className="app-bar">
<MarkdownEditor.Toolbar>
<MarkdownEditor.FormatButtons />
<MarkdownEditor.ToolbarDivider />
<MarkdownEditor.InsertButtons />
</MarkdownEditor.Toolbar>
<MyAppButtons />
</header>
<MarkdownEditor.Content placeholder="Write…">
<MarkdownEditor.Outline />
</MarkdownEditor.Content>
</MarkdownEditor.Root>
| Part | Role |
|---|---|
Root | Lexical composer + plugins. Takes value, onChange, mode, readOnly, autoFocus, className, measure, defaultBlockWidth, drawingStyle |
Content | The editable surface. Takes placeholder, foldable; children are docked sidebars |
Toolbar | Container; renders the default groups when empty |
FormatButtons, InsertButtons, HistoryButtons | Built-in groups |
ToolbarButton, ToolbarDivider | Primitives for your own items |
Outline | Table-of-contents sidebar |
The same parts are exported at the top level (Toolbar, ToolbarButton,
ToolbarDivider, FormatButtons, InsertButtons, HistoryButtons) for
code that prefers named imports.
Here the toolbar sits in an app bar next to a Save button, and the outline
is docked inside Content:
Headless: useMarkdownEditor()
For fully custom UI (buttons in your own design system, keyboard
shortcuts, status bars), call the hook from any component rendered under
MarkdownEditor.Root (also available as MarkdownEditor.useEditor):
import { useMarkdownEditor } from '@zuilib/text-editor'
function BoldButton() {
const { activeFormats, toggleFormat } = useMarkdownEditor()
return (
<MyButton pressed={activeFormats.has('bold')} onClick={() => toggleFormat('bold')}>
B
</MyButton>
)
}
It returns a MarkdownEditorApi:
| Member | Type | Notes |
|---|---|---|
editor | LexicalEditor | The underlying instance, for dispatching your own commands |
activeFormats | ReadonlySet<TextFormatType> | Formats active at the selection (bold, italic, underline, strikethrough, code) |
toggleFormat(format) | ||
insertTable(options?) | { rows?, columns?, width? } | width defaults to defaultBlockWidth.table; when given it is written explicitly, even full |
insertDrawing(options?) | { width? } | Same rule with defaultBlockWidth.drawing |
blockWidth / setBlockWidth(width) | BlockWidth | null | Width of the block containing the selection: the focused drawing, or else the table the caret is in; null outside both |
tableDensity / setTableDensity(density) | TableDensity | null | null outside tables |
tableCell | TableCellPosition | null | { row, column, rows, columns, hasHeader } for the selection's cell |
insertTableRow(position?) | 'above' | 'below' (default below) | Never inserts above the header row: above there inserts right below it |
deleteTableRow() | No-op on the last row; deleting the header promotes the next row | |
insertTableColumn(position?) | 'before' | 'after' (default after) | |
deleteTableColumn() | No-op on the last column | |
canUndo, canRedo, undo(), redo() | ||
tableWidth / setTableWidth | Deprecated table-only aliases of blockWidth / setBlockWidth |
Insertions and deletions move the caret into whatever was created. The rules behind the table members are in Tables.
Every button below is a plain <button> driven by the hook; the status on
the right reads activeFormats and tableCell: