ExamplesCustom SchemasCallout Block

Callout Block

In this example, we create a custom Callout block with a real rich-text title and a body of child blocks (a compartment), like a Notion-style callout.

The block combines content: "inline" with the children config on BlockConfig. The title is ordinary inline content — formatting, links, and multiplayer cursors all work — while children: { allow: "blocks" } hosts the body blocks, which live on block.children at runtime. render draws the title row and renderFrame draws the box around the title and body together.

We also wire up a Slash Menu item to insert the callout, and render the document JSON next to the editor so you can inspect the structure of the titled block and its nested children.

Try it out:

  • Press Enter at the end of the callout's title to jump into its body.
  • Press Backspace at the start of the first body block to merge it back into the title.
  • Press "/" inside the body and add a code block, heading, or list.
  • Watch the JSON panel on the right update as you edit; the title is content and the body is block.children.

Relevant Docs:

import { BlockNoteSchema, defaultBlockSpecs } 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 { useEffect, useState } from "react";import { RiChatQuoteLine } from "react-icons/ri";import { createCallout } from "./Callout";import "./styles.css";// Schema with the default blocks plus our custom Callout compartment block.const schema = BlockNoteSchema.create().extend({  blockSpecs: {    ...defaultBlockSpecs,    callout: createCallout(),  },});// Slash menu item to insert a Callout.const insertCallout = (editor: typeof schema.BlockNoteEditor) => ({  title: "Callout",  subtext: "Titled container block that wraps other blocks",  onItemClick: () =>    insertOrUpdateBlockForSlashMenu(editor, {      type: "callout",    }),  aliases: ["callout", "container", "alert", "note", "tip", "info"],  group: "Basic blocks",  icon: <RiChatQuoteLine />,});type AppBlock = (typeof schema.BlockNoteEditor)["document"][number];export default function App() {  const [blocks, setBlocks] = useState<AppBlock[]>([]);  const editor = useCreateBlockNote({    schema,    initialContent: [      {        type: "paragraph",        content:          "Welcome! This demo shows a compartment block: a rich-text title with a body of child blocks.",      },      {        type: "callout",        content: "A callout with a real title",        children: [          {            type: "paragraph",            content:              "The title is ordinary inline content: formatting, links, and multiplayer cursors all work.",          },          {            type: "paragraph",            content:              "Press Enter at the end of the title to jump into the body, or Backspace at the start of the body to merge back.",          },        ],      },      {        type: "paragraph",        content: "Press '/' anywhere to insert a new Callout.",      },      {        type: "paragraph",      },    ],  });  useEffect(() => setBlocks(editor.document), [editor]);  return (    <div className={"wrapper"}>      <div>BlockNote Editor:</div>      <div className={"item"}>        <BlockNoteView          editor={editor}          slashMenu={false}          onChange={() => {            setBlocks(editor.document);          }}        >          <SuggestionMenuController            triggerCharacter={"/"}            getItems={async (query) => {              const defaultItems = getDefaultReactSlashMenuItems(editor);              const lastBasicBlockIndex = defaultItems.findLastIndex(                (item) => item.group === "Basic blocks",              );              defaultItems.splice(                lastBasicBlockIndex + 1,                0,                insertCallout(editor),              );              return filterSuggestionItems(defaultItems, query);            }}          />        </BlockNoteView>      </div>      <div>Document JSON:</div>      <div className={"item bordered"}>        <pre>          <code>{JSON.stringify(blocks, null, 2)}</code>        </pre>      </div>    </div>  );}