Callout Block
In this example, we create a custom Callout block with a real rich-text title and a body of child blocks (a titled block), 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.
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.
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 { createCallout } from "./Callout";import "./styles.css";// Schema with the default blocks plus our custom Callout titled block.const schema = BlockNoteSchema.create().extend({ blockSpecs: { callout: createCallout(), },});// Slash menu item to insert a Callout.function insertCallout(editor: typeof schema.BlockNoteEditor) { return { 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 />, };}export default function App() { const editor = useCreateBlockNote({ schema, initialContent: [ { type: "paragraph", content: "Welcome! This demo shows a titled 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", }, ], }); return ( <BlockNoteView editor={editor} slashMenu={false}> <SuggestionMenuController triggerCharacter={"/"} getItems={async (query) => filterSuggestionItems( [...getDefaultReactSlashMenuItems(editor), insertCallout(editor)], query, ) } /> </BlockNoteView> );}import { createReactBlockSpec } from "@blocknote/react";import "./styles.css";// The Callout block: a titled block. `content: "inline"` plus `children`// gives the block its own rich-text title with child blocks as its body.// `render` draws the title row (the title mounts into `contentRef`), while// `renderFrame` draws the box around the title and the body together.export const createCallout = createReactBlockSpec( { type: "callout", propSchema: {}, content: "inline", // The title is ordinary inline content; the children are the body. children: { allow: "blocks" }, }, { render: (props) => ( <div className={"callout-title-row"}> <span className={"callout-badge"} contentEditable={false}> ! </span> <span className={"callout-title"} ref={props.contentRef} /> </div> ), renderFrame: (props) => ( <div className={"callout"}> <div className={"callout-slot"} ref={props.contentRef} /> </div> ), },);.callout { border-radius: 6px; padding: 12px 16px; border-left: 4px solid var(--callout-accent, #507aff); background-color: var(--callout-bg, #e6ebff);}[data-color-scheme="dark"] .callout { --callout-bg: #1e2a5c;}.callout-title-row { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 4px;}.callout-badge { flex-shrink: 0; width: 20px; height: 20px; border-radius: 9999px; background-color: var(--callout-accent, #507aff); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; margin-top: 2px;}.callout-title { flex-grow: 1; min-width: 0; font-weight: 600;}.callout-slot { min-width: 0;}