Nested Blocks
Blocks within blocks
Nested blocks let a block contain other blocks. Use them for layouts like sections, cards, accordions, button rows, reusable rich-text areas, and template-part slots.
The <InnerBlocks /> component is the child block container.
import { defineBlock, EditableText, InnerBlocks } from "eddev/blocks"
export const meta: BlockMeta = {
title: "Section",
tags: ["#root"],
}
export default defineBlock("content/section", () => {
return (
<section className="grid-auto">
<EditableText as="h2" store="title" defaultValue="Enter a title" />
<InnerBlocks allowedBlocks={["#inline"]} />
</section>
)
})You can only have one InnerBlocks in a block. This is a WordPress/Gutenberg limitation. If a block needs multiple editable zones, see SlotBlocks below.
Allowed Blocks
Use allowedBlocks to control what can be inserted inside the container.
<InnerBlocks allowedBlocks={["core/paragraph", "core/list", "content/button-row"]} />Entries can be specific block names or tags. Tags are usually cleaner when several blocks should be allowed in the same context.
export const meta: BlockMeta = {
title: "Button Row",
tags: ["#inline"],
}
export default defineBlock("content/button-row", () => {
return <InnerBlocks allowedBlocks={["#buttons"]} />
})export const meta: BlockMeta = {
title: "Button",
tags: ["#buttons"],
}WordPress core blocks can also be tagged and allowed this way. See Core Blocks for the PHP-side setup.
The unprefixed root tag is the framework default, but our examples use explicit #root and set rootBlocks in blocks/_editor.tsx.
Templates And Locking
InnerBlocks can insert default child blocks and control how much authors can change the list.
<InnerBlocks
allowedBlocks={["content/card-grid-item"]}
defaultBlocks={[["content/card-grid-item", {}]]}
templateLock="insert"
/>defaultBlocksare inserted when the container is empty.headerTemplateandfooterTemplatekeep blocks pinned to the start or end.templatedefines the whole child block list.templateLock="all"prevents moving, inserting, or deleting.templateLock="insert"prevents inserting or removing, but allows moving existing blocks.templateLock="contentOnly"locks structure but allows rich text editing.templateLock="none"orfalseleaves the list editable.
Use these sparingly. A good rule is to lock structural wrapper blocks, but keep content blocks flexible.
Appenders And Editor Classes
adminClassName adds a class to the editor-only wrapper. appender changes the inserter UI.
<InnerBlocks
allowedBlocks={["content/card-grid-item"]}
adminClassName="grid grid-cols-3 gap-4"
appender={{ type: "simple" }}
/>Supported appender types are default, button, button2, simple, or a custom appender created with createAppender.
Rendering Children Yourself
Most blocks can render <InnerBlocks /> directly. If the parent needs to inspect child blocks, use useInnerBlocks().
import { defineBlock, InnerBlocks, useInnerBlocks } from "eddev/blocks"
export default defineBlock("content/card-grid", () => {
const blocks = useInnerBlocks()
const columns = blocks.length === 3 ? "grid-cols-3" : "grid-cols-2"
return (
<div className={columns}>
<InnerBlocks allowedBlocks={["content/card-grid-item"]} />
</div>
)
})useInnerBlocks() returns block data with metadata added, including slug, tags, and flags.
Multiple Editable Zones
Use SlotBlocks only when a block genuinely needs multiple independent editable zones.
import { SlotBlocks } from "eddev/blocks"
<SlotBlocks id="primary" allowedBlocks={["#menu"]} />
<SlotBlocks id="cta" template={[["content/button", {}]]} templateLock="insert" />SlotBlocks stores hidden core/slot-group blocks inside the parent and renders each slot separately. It is useful for complex headers, mega menus, or app-like layouts, but it is more complex than a normal InnerBlocks container.