Layout Block
In this example, we create a custom Panel block that holds other blocks as its body, such as a panel containing headings and paragraphs.
The block sets layoutOnly: true on BlockConfig, which makes it a layout block: it has no content of its own, only child blocks. It draws its box in renderFrame, and its child blocks go where slotRef is attached. At runtime, they are in block.children.
We also wire up a Slash Menu item to insert the panel.
Try it out:
- Press the "/" key inside the panel's body and add a code block, heading, or list.
- Insert a new panel via the Slash Menu (search "panel").
Relevant Docs:
import { BlockNoteSchema } from "@blocknote/core";import { filterSuggestionItems, insertOrUpdateBlockForSlashMenu,} from "@blocknote/core/extensions";import "@blocknote/core/fonts/inter.css";import { BlockNoteView } from "@blocknote/mantine";import "@blocknote/mantine/style.css";import { SuggestionMenuController, getDefaultReactSlashMenuItems, useCreateBlockNote,} from "@blocknote/react";import { RiChatQuoteLine } from "react-icons/ri";import { createPanel } from "./Panel";import "./styles.css";// Schema with the default blocks plus our custom Panel layout block.const schema = BlockNoteSchema.create().extend({ blockSpecs: { panel: createPanel(), },});// Slash menu item to insert a Panel. Inserting one with no children fills// it with an empty paragraph, as `min` defaults to 1.function insertPanel(editor: typeof schema.BlockNoteEditor) { return { title: "Panel", subtext: "Layout block that wraps other blocks", onItemClick: () => insertOrUpdateBlockForSlashMenu(editor, { type: "panel", }), aliases: [ "panel", "layout", "container", "callout", "alert", "note", "tip", "info", ], group: "Basic blocks", icon: <RiChatQuoteLine />, };}export default function App() { const editor = useCreateBlockNote({ schema, initialContent: [ { type: "paragraph", content: "Welcome! This demo shows the new layout block kind.", }, { type: "panel", children: [ { type: "heading", props: { level: 3 }, content: "More than paragraphs", }, { type: "paragraph", content: "Panels can hold any block as their body.", }, { type: "paragraph", content: "Try pressing '/' inside this panel to add a heading or code block.", }, ], }, { type: "paragraph", content: "Press '/' anywhere to insert a new Panel.", }, { type: "paragraph", }, ], }); return ( <BlockNoteView editor={editor} slashMenu={false}> <SuggestionMenuController triggerCharacter={"/"} getItems={async (query) => filterSuggestionItems( [...getDefaultReactSlashMenuItems(editor), insertPanel(editor)], query, ) } /> </BlockNoteView> );}import { createReactBlockSpec } from "@blocknote/react";import "./styles.css";export const createPanel = createReactBlockSpec( { type: "panel", propSchema: {}, content: "none", layoutOnly: true, }, { // With no content of its own, the panel draws only its frame. The slot // (`slotRef`) receives its child blocks. renderFrame: (props) => <div className="panel" ref={props.slotRef} />, },);.panel { border-left: 4px solid #507aff; border-radius: 6px; background-color: var(--panel-bg, #e6ebff); padding: 12px 16px;}[data-color-scheme="dark"] .panel { --panel-bg: #1e2a5c;}