ExamplesCustom SchemasAlert Blocks

Alert Blocks

In this example, we create two Alert variants that hold other blocks as their body. The alert block keeps its title as inline content with child blocks as its body (a compartment), while alertBox holds only child blocks (a pure container).

Both variants declare the children config on BlockConfig with children: { allow: "blocks" }. The titled alert additionally uses render for its title row and renderFrame for the box around the title and body together; the title-less alert uses renderFrame alone, with the children mounting straight into its slot.

We also wire up Slash Menu items to insert each variant, and render the document JSON next to the editor so you can inspect the structure of the nested blocks.

Try it out:

  • Press Enter at the end of the alert's title to start its body, keeping the existing body blocks in place.
  • Press Backspace at the start of the first body block to merge it back into the title.
  • Press "/" anywhere to insert a new Alert or Alert box, and watch the JSON panel update as you edit.

Relevant Docs:

import { createBlockSpec } from "@blocknote/core";import "./styles.css";const alertFlavors = ["info", "warning", "success"] as const;// Alert WITH a title: a compartment. The title is the block's own inline// content, and the body is its `children` - other blocks that belong to it.// `render` draws the title row (the title mounts into `contentDOM`), while// `renderFrame` draws the box around the title and the body together: both// render into the frame's `slot`.//// Flavor-dependent styling lives in `renderFrame`, not `render`: the frame// rebuilds whenever the block's props change, so reading// `block.props.flavor` there is always fresh.export const createAlert = createBlockSpec(  {    type: "alert" as const,    propSchema: {      flavor: {        default: "info",        values: [...alertFlavors],      },    },    content: "inline",    children: { allow: "blocks" },  },  {    render: () => {      const dom = document.createElement("div");      dom.className = "alert-title-row";      const badge = document.createElement("span");      badge.className = "alert-badge";      badge.textContent = "!";      badge.contentEditable = "false";      const contentDOM = document.createElement("span");      contentDOM.className = "alert-title";      dom.append(badge, contentDOM);      return { dom, contentDOM };    },    renderFrame: (block) => {      const dom = document.createElement("div");      dom.className = "alert";      dom.dataset.flavor = block.props.flavor;      const slot = document.createElement("div");      slot.className = "alert-slot";      dom.append(slot);      return {        dom,        slot,        update: (newBlock) => {          dom.dataset.flavor = newBlock.props.flavor;        },      };    },  },);// Alert WITHOUT a title: a pure container. There is no content of its own,// only a body of child blocks, so `renderFrame` alone draws the box and the// children mount straight into it (`slot: dom`).export const createAlertBox = createBlockSpec(  {    type: "alertBox" as const,    propSchema: {      flavor: {        default: "info",        values: [...alertFlavors],      },    },    content: "none",    children: { allow: "blocks" },  },  {    renderFrame: (block) => {      const dom = document.createElement("div");      dom.className = "alert";      dom.dataset.flavor = block.props.flavor;      return {        dom,        slot: dom,        update: (newBlock) => {          dom.dataset.flavor = newBlock.props.flavor;        },      };    },  },);