Skip to main content

Layout

By default every block spans the content pane. Set a measure 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} onChange={setValue} measure="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.

Adopting a measure

Set measure (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 measure 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 measure 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

defaultBlockWidth 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
measure="48rem"
defaultBlockWidth={{ table: 'text', drawing: 'text' }}
value={value}
onChange={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 measure, otherwise whatever centres the column
--zui-text-editor-bleedHow far a full-width block extends past the column: 0 without a measure

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.