BlockNote DocsFeaturesCustom SchemasLayout Blocks

Layout Blocks

Preview

Layout blocks are a preview feature. The API can change in a future release. We would like to hear what you build with layout blocks, and what does not work for you. Please tell us in GitHub Discussions or open an issue.

A layout block is a custom block that holds and arranges other blocks, but has no content of its own. Panels, tab sets and column layouts are examples. Its child blocks are inside the layout block, not indented below it, and the layout block draws a frame around them. A layout block can also limit which blocks it holds, for example only tabs in a tab set.

If your block has its own text and also child blocks, for example an alert with a title and a body, use a custom block with a frame. See Layout Blocks and Block Frames for the differences.

Take a look at the demo below, in which we add a custom panel to a BlockNote editor, as well as a custom Slash Menu item to insert it. Each panel can contain paragraphs, headings, lists, or any other blocks in the editor.

For more advanced layout blocks, see the Tabs Block and Stepper Block examples.

Creating a Layout Block

Use the createReactBlockSpec function to create a layout block, just like a Custom Block. For the panel below, we set content to "none" and layoutOnly to true, so the panel's child blocks go inside it:

import { createReactBlockSpec } from "@blocknote/react";

export const createPanel = createReactBlockSpec(
  {
    type: "panel",
    propSchema: {},
    content: "none",
    layoutOnly: true,
  },
  {
    renderFrame: (props) => (
      <div className="panel" ref={props.slotRef} />
    ),
  },
);

Block Config

The block config defines the content and child blocks your layout block can hold:

content: Must be "none" for a layout block. A layout block holds only its child blocks. For a block with its own text and child blocks, see Layout Blocks and Block Frames.

layoutOnly: Set to true to put the block's child blocks inside it. Without it, a block's child blocks are indented below it. A layout block accepts any block by default. You can also restrict it to specific layout block types, as explained in Restricting Children.

propSchema: Defines the layout block's props, just like for other custom blocks. Use these to customize its appearance or behavior.

Block Implementation

renderFrame: Your React component draws the layout block's frame. A layout block has no content of its own, so it has no render, only this frame. renderFrame takes the same props as for other blocks, but it must always draw the frame: it must not return null. Attach slotRef to the element that holds the child blocks. You can add icons, buttons, or other elements around this element:

renderFrame: (props) => (
  <div className="panel">
    <span contentEditable={false}>💡</span>
    <div ref={props.slotRef} />
  </div>
),

You can style the component with CSS, just like any other React component:

.panel {
  display: flex;
  gap: 12px;
  padding: 16px;
  border-left: 4px solid #507aff;
  border-radius: 6px;
}

Adding Layout Blocks to the Editor

Add your layout block to a BlockNote schema:

import { BlockNoteSchema } from "@blocknote/core";
import { createPanel } from "./Panel";

const schema = BlockNoteSchema.create().extend({
  blockSpecs: {
    panel: createPanel(),
  },
});

You can then create an editor with this schema, as explained on the Custom Schemas page. Use children to set the blocks inside a panel:

import { useCreateBlockNote } from "@blocknote/react";

const editor = useCreateBlockNote({
  schema,
  initialContent: [
    {
      type: "panel",
      children: [
        { type: "heading", content: "Getting started" },
        { type: "paragraph", content: "Follow these steps to get set up." },
        { type: "checkListItem", content: "Create an account" },
      ],
    },
  ],
});

If you create a panel without specifying its children, it starts with an empty paragraph. To let users insert panels themselves, add a custom Slash Menu item, as shown in the demo.

To add blocks to an existing layout block, see Inserting Blocks.

Layout Blocks and Block Frames

Every custom block can have a frame, drawn by renderFrame around the block's content and its child blocks. A layout block is a block that has only a frame. The table shows the differences:

Block with a frameLayout block
ExampleAlert with a title and a bodyPanel, tab set, stepper, column list
ConfigAny content, usually "inline" or "plain"content: "none" and layoutOnly: true
ContentIts own text, drawn by renderNone
Implementationrender (mandatory) draws the content. renderFrame (optional) draws the frame. It can return null to show the block without a framerenderFrame (mandatory) draws the frame. It must always draw a frame: if it returns null, export fails
Child blocksAny blocksAny blocks, or only the layout block types in children.allow

Use a layout block when the block only holds and arranges other blocks. Use a block with a frame when the block has its own text, for example a title.

Restricting Children

For structured layouts, you can limit a layout block to specific layout block types, with these options in the block config:

children.allow: Accepts "blocks" (the default) or an array of layout block type names. At this moment, you cannot list regular block types such as "paragraph" individually.

children.min: The minimum number of children, for an allow list only. Defaults to 1.

children.dissolve: Whether a layout block with too few children dissolves into what they hold, for an allow list only. Defaults to true, like a column list with one column left. Set false to keep the layout block and its empty children, like a tab set.

children.exclude: For a layout block that accepts any blocks, the layout block types it does not hold. Only layout block types can be excluded, not regular block types such as "paragraph".

placeable: Set to "namedOnly" to restrict a layout block to parents that name it in children.allow. Defaults to "anywhere".

The Tabs Block and Stepper Block examples use these options. A tab set holds only tab blocks, and a stepper holds only step blocks. Both set dissolve: false, so they stay when the user removes their last tab or step. A tab and a step are placeable: "namedOnly", so they can only be in a tab set or a stepper.

These options need layoutOnly: true. Other blocks can always have child blocks of any type, so for them children can only be the default, { allow: "blocks" }.

For built-in column blocks, see Multi-Column Layouts.

Exporting Layout Blocks

To export a document with your layout block to DOCX, ODT, PDF, Typst or email, give the exporter a mapping for the block. The mapping for a layout block must be a { withChildren } mapping. A function mapping causes an error, because the exporter does not know where the child blocks go.

withChildren gets the child blocks, which the exporter has already converted. Put them inside the result for the block. For example, for Typst:

new TypstExporter(schema, {
  ...typstDefaultSchemaMappings,
  blockMapping: {
    ...typstDefaultSchemaMappings.blockMapping,
    panel: {
      withChildren: (block, exporter, nestingLevel, numberedListIndex, children) =>
        // Draws a box around the child blocks. Each child block is Typst
        // markup, and a blank line keeps them as separate blocks.
        `#block(stroke: 1pt, inset: 8pt)[${children.join("\n\n")}]`,
    },
  },
});

The other exporters work the same way, but give children in their own format, for example docx elements for DOCX.