Skip to main content

Layout

By default every block spans the content pane. Set a max text width to get a readable text column instead: paragraphs, headings, lists, quotes, code and frontmatter are capped at that width and centred in the pane, while each table and drawing chooses its own width.

<MarkdownEditor value={value} onValueChange={setValue} maxTextWidth="48rem" />

The prop is sugar for the CSS custom property --zui-text-editor-measure, which is the actual contract. Set it in a stylesheet on .zui-text-editor (or any ancestor) instead of passing the prop:

.zui-text-editor {
--zui-text-editor-measure: 48rem;
}

The pane itself is never capped; full-width blocks stop short of the outline sidebar; the placeholder and fold chevrons follow the column.

All of this responds to the pane's width, not the viewport: below 40rem the outline becomes a collapsible strip above the document, tables scroll horizontally inside their own box, and a drawing wider than the pane scales down to fit.

Adopting a max text width

Set maxTextWidth (or the variable) and delete any external max-width / margin-inline overrides on .zui-text-editor-content > *; the library owns that layout.

The example is narrower than a real page, so the max text width is set to 28rem here; toggle it off to see every block span the pane:

Loading editor

Block width​

Every table and drawing has a BlockWidth, picked from the floating table toolbar or the right end of the drawing toolbar:

BlockWidthLayoutMarkdown
fullThe content pane, edge to edge (never under the outline sidebar). The defaultOmitted
textThe text column: edges align with the paragraphs. Identical to full until a max text width is set<!-- width: text --> / "width":"text"
contentShrinks to its columns / shapes, left-aligned with the text, never wider than the column<!-- width: content --> / "width":"content"

Absence of a marker always means full, in every app.

Insertion defaults​

newBlockWidth only changes what the toolbar, insertTable() and insertDrawing() write into new blocks. That value is written explicitly, even when it is full, so the document reads the same in an app with other defaults; a width named in the source (<!-- width: full -->, "width":"full") is preserved on round trip.

<MarkdownEditor
maxTextWidth="48rem"
newBlockWidth={{ table: 'text', drawing: 'text' }}
value={value}
onValueChange={setValue}
/>

Headless: useMarkdownEditor().blockWidth / setBlockWidth act on the block containing the selection: the focused drawing, or else the table the caret is in. BLOCK_WIDTHS lists the values and isBlockWidth() is the type guard.

How the CSS works​

The layout lives in zero-specificity :where() rules, so a single class rule of yours overrides it. .zui-text-editor-main is a container and derives two custom properties:

PropertyMeaning
--zui-text-editor-gutterThe pane's horizontal padding: 2rem without a max text width, otherwise whatever centres the column
--zui-text-editor-bleedHow far a full-width block extends past the column: 0 without a max text width

Each top-level block reads --zui-text-editor-block-bleed, so one rule changes any block kind:

/* Code blocks span the pane like tables do */
.zui-code { --zui-text-editor-block-bleed: var(--zui-text-editor-bleed); }

/* Every table stays inside the text column */
.zui-table { --zui-text-editor-block-bleed: 0px; }

Block classes: .zui-paragraph, .zui-heading (+ .zui-heading-1 … .zui-heading-6), .zui-list (+ .zui-list-ordered / .zui-list-unordered, .zui-checklist), .zui-quote, .zui-code, .zui-frontmatter, .zui-table, .zui-drawing. A drawing at text width also carries .zui-drawing-canvas.is-text-width.