Custom Block Types
In addition to the default block types that BlockNote offers, you can also make your own custom blocks using React components. Take a look at the demo below, in which we add a custom alert block to a BlockNote editor, as well as a custom Slash Menu Item to insert it.
import {
BlockConfig,
DefaultInlineContentSchema,
defaultProps,
DefaultStyleSchema,
} from "@blocknote/core";
import {
createReactBlockSpec,
ReactCustomBlockRenderProps,
useContent,
} from "@blocknote/react";
import { Menu } from "@mantine/core";
import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md";
import "./styles.css";
// The types of alerts that users can choose from.
export const alertTypes = [
{
title: "Warning",
value: "warning",
icon: MdError,
color: "#e69819",
backgroundColor: {
light: "#fff6e6",
dark: "#805d20",
},
},
{
title: "Error",
value: "error",
icon: MdCancel,
color: "#d80d0d",
backgroundColor: {
light: "#ffe6e6",
dark: "#802020",
},
},
{
title: "Info",
value: "info",
icon: MdInfo,
color: "#507aff",
backgroundColor: {
light: "#e6ebff",
dark: "#203380",
},
},
{
title: "Success",
value: "success",
icon: MdCheckCircle,
color: "#0bc10b",
backgroundColor: {
light: "#e6ffe6",
dark: "#208020",
},
},
] as const;
const alertBlockConfig = {
type: "alert",
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
type: {
default: "warning",
values: ["warning", "error", "info", "success"],
},
},
content: "inline",
} satisfies BlockConfig;
const RenderAlert = (
props: ReactCustomBlockRenderProps<
typeof alertBlockConfig,
DefaultInlineContentSchema,
DefaultStyleSchema
>
) => {
const contentProps = useContent();
const alertType = alertTypes.find((a) => a.value === props.block.props.type)!;
const Icon = alertType.icon;
return (
<div className={"alert"} data-alert-type={props.block.props.type}>
{/*Icon which opens a menu to choose the Alert type*/}
<Menu withinPortal={false} zIndex={999999}>
<Menu.Target>
<div className={"alert-icon-wrapper"} contentEditable={false}>
<Icon
className={"alert-icon"}
data-alert-icon-type={props.block.props.type}
size={32}
/>
</div>
</Menu.Target>
{/*Dropdown to change the Alert type*/}
<Menu.Dropdown>
<Menu.Label>Alert Type</Menu.Label>
<Menu.Divider />
{alertTypes.map((type) => {
const ItemIcon = type.icon;
return (
<Menu.Item
key={type.value}
leftSection={
<ItemIcon
className={"alert-icon"}
data-alert-icon-type={type.value}
/>
}
onClick={() =>
props.editor.updateBlock(props.block, {
type: "alert",
props: { type: type.value },
})
}>
{type.title}
</Menu.Item>
);
})}
</Menu.Dropdown>
</Menu>
{/*Rich text field for user to type in*/}
<div className={"inline-content"} {...contentProps} />
</div>
);
};
// The Alert block.
export const Alert = createReactBlockSpec(alertBlockConfig, {
render: RenderAlert,
});
Creating a Custom Block Type
Use the createReactBlockSpec
function to create a custom block type. This function takes two arguments:
function createReactBlockSpec(
blockConfig: CustomBlockConfig,
blockImplementation: ReactCustomBlockImplementation,
);
Let's look at our custom alert block from the demo, and go over each field to explain how it works:
const Alert = createReactBlockSpec(
{
type: "alert",
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
type: {
default: "warning",
values: ["warning", "error", "info", "success"],
},
},
content: "inline",
},
{
render: (props) => {
...
},
}
);
Block Config (CustomBlockConfig
)
The Block Config describes the shape of your custom blocks. Use it to specify the type, properties (props) and content your custom blocks should support:
type CustomBlockConfig = {
type: string;
content: "inline" | "none";
readonly propSchema: PropSchema;
};
type
Defines the identifier of the custom block.
content
inline
if your custom block should support rich text content, none
if not.
In the alert demo, we want the user to be able to type text in our alert, so we set it to "inline"
.
propSchema
The PropSchema
specifies the props that the block supports. Block props (properties) are data stored with your Block in the document, and can be used to customize its appearance or behavior.
type PropSchema<
PrimitiveType extends "boolean" | "number" | "string"
> = Record<
string,
{
default: PrimitiveType;
values?: PrimitiveType[];
}
>
[key: string]
is the name of the prop, and the value is an object with two fields:
-
default
: Specifies the prop's default value -
values
(optional): Specifies an array of values that the prop can take, for example, to limit the value to a list of pre-defined strings. Ifvalues
is not defined, BlockNote assumes the prop can be any value ofPrimitiveType
In the alert demo, we add a type
prop for the type of alert that we want (warning / error / info / success). We also want basic styling options, so we add text alignment and text color from the Default Block Properties.
Block Implementation (ReactCustomBlockImplementation
)
The Block Implementation defines how the block should be rendered in the editor, and how it should be parsed from and converted to HTML.
type ReactCustomBlockImplementation = {
render: React.FC<{
block: Block;
editor: BlockNoteEditor;
contentRef?: (node: HTMLElement | null) => void;
}>;
toExternalHTML?: React.FC<{
block: Block;
editor: BlockNoteEditor;
contentRef?: (node: HTMLElement | null) => void;
}>;
parse?: (element: HTMLElement) => PartialBlock["props"] | undefined;
};
render
This is your React component which defines how your custom block should be rendered in the editor, and takes three React props:
block:
The block that should be rendered. Its type and props will match the type and PropSchema defined in the Block Config.
editor:
The BlockNote editor instance that the block is in.
contentRef:
A React ref
you can use to mark which element in your block is editable, this is only available if your block config contains content: "inline"
.
toExternalHTML
(optional)
This component is used whenever the block is being exported to HTML for use outside BlockNote, for example when copying it to the clipboard. If it's not defined, BlockNote will just use render
for the HTML conversion.
Note that your component passed to toExternalHTML
is rendered and serialized in a separate React root, which means you can't use hooks that rely on React Contexts.
parse
(optional)
The parse
function defines how to parse HTML content into your block, for example when pasting contents from the clipboard. If the element should be parsed into your custom block, you return the props that the block should be given. Otherwise, return undefined
.
element
: The HTML element that's being parsed.
Adding Custom Blocks to the Editor
Finally, create a BlockNoteSchema using the definition of your custom blocks:
const schema = BlockNoteSchema.create({
blockSpecs: {
// enable the default blocks if desired
...defaultBlockSpecs,
// Add your own custom blocks:
alert: Alert,
},
});
You can then instantiate your editor with this custom schema, as explained on the Custom Schemas page.