Container blocks
Many blocks hold other blocks: a section holds teasers, tabs hold one panel per tab, a slider holds slides. In WordPress these are blocks with inner blocks. A container decides which blocks may go inside, which ones a new container starts with, and how editors may change them. The blocks inside often depend on their container, too: a tab only makes sense inside tabs, and it needs to know which tab is active.
This page shows how to build such a pair of blocks. The basics of a block – attribute class, template, render() – are explained in Blocks.
A container and its children
A block becomes a container when its render() accepts string $content, the rendered inner blocks. Its template marks the element that holds them with inner_blocks():
Without further declarations every block may be inserted into it, and the editor behaves as for core/group. #[InnerBlocks] changes that.
Inner blocks
#[InnerBlocks] on the block class describes the inner blocks:
allowedlimits the blocks that can be inserted. Here only tabs.templateis what a newly inserted container starts with: two tabs with their titles.templateLockrestricts changes.TemplateLock::Insertlets editors reorder the tabs, but not add or remove them;TemplateLock::Allallows no changes to the structure,TemplateLock::ContentOnlyonly changes of the content. Without it the container inherits the lock of its parent, andTemplateLock::Unlockedlifts a lock of the parent.orientationtells the editor that the tabs sit side by side. The movers then show arrows to the left and right.
Three more options control how blocks are added. defaultBlock is the block the editor suggests, and with directInsert: true the "+" at the end of the container inserts it right away. prioritized lists blocks that the quick inserter of the container – the small "+" – and the slash command offer first. appender chooses that "+" at the end: Appender::Default lets WordPress decide (an empty paragraph to type into, or the "+" button if paragraphs are not allowed), Appender::Button always shows the button, and Appender::None shows nothing, so blocks are added through the toolbar or the list view only.
With TemplateLock::All or TemplateLock::ContentOnly, the editor brings the inner blocks of every container in line with the template whenever it opens one. Inner blocks that differ – in a post, a template or a pattern – are replaced. The build reports templates, template parts and patterns whose inner blocks differ from a locked template.
Referring to blocks
allowed and the other options name blocks in three ways, so that your IDE can resolve them wherever possible:
- blocks of your theme or plugin by their class,
Tab::class; - core blocks by a case of
CoreBlock,CoreBlock::Paragraph; - other blocks by their full name,
'acme/slider'.
A TemplateBlock takes the block in the same way, its attribute values and, for a container, its inner blocks: new TemplateBlock(Tab::class, ['title' => 'Tab 1'], [new TemplateBlock(CoreBlock::Paragraph)]). Attribute values of your own blocks are checked in the build against their attribute class; a backed enum stands for its value.
Restricting where a block goes
A tab outside of tabs makes no sense. parent on the child limits it to the containers it may be inserted into directly; the inserter then offers it only there:
ancestor is the looser variant: the block may be inserted anywhere inside the named blocks, also deeper down. Both take the same values as allowed. The build checks that each container of your own in parent allows the child, and it reports your blocks placed elsewhere in templates, template parts and patterns.
Passing values to inner blocks
The tabs know which tab is active; each tab needs that value to render itself open or closed. WordPress passes such values as block context: a container provides values of its attributes, and the blocks inside read them, at any depth, without storing them themselves.
The container marks the attributes it passes on with #[ProvidesContext] in its attribute class:
A block inside reads the value with a parameter of render() marked #[Context]. It names the container class and its attribute:
The parameter receives the value with its type, created like an attribute: an enum as its case, a value class as an object. Outside of tabs there is no value, and the parameter keeps its default; that's why it needs a default or a nullable type. In the editor the value reaches the block as well: when an editor changes the active tab, the tabs inside render again.
The attribute name is the only string here, and the build checks it: the container must be a block of your theme or plugin, its attribute class must have the attribute, the attribute must be marked with #[ProvidesContext], and the parameter must have the attribute's type.
WordPress itself names context values; the framework derives the names, {namespace}/{block}/{attribute} – here sections/tabs/active. Values of other blocks, such as postId in a query loop, are read as before with usesContext and WP_Block (see Blocks).
Rules
A block reference is a block class of the same theme or plugin, a CoreBlock case or the full name of another block. The build reports:
#[InnerBlocks]on a block whoserender()takes nostring $content;- a class that is no block of the theme or plugin, a name without
/, a name in your own namespace (name those blocks by class) and a core block named as string althoughCoreBlockhas a case for it – strings are only for core blocks newer than the framework; - template blocks, a default block or prioritized blocks that the container does not allow, inner blocks in a template block of a block without inner blocks, a default block with inner blocks, and
directInsertwithoutdefaultBlock; - unknown or invalid attribute values of your own blocks in a
TemplateBlock; parentnaming a container of your own that does not allow the block.
#[Block(parent: null, ancestor: null)] takes lists of block references.
Context:
#[ProvidesContext]marks parameters of a block attribute class only, not of value classes or settings groups. The value is passed as context{namespace}/{block}/{attribute}.#[Context(Block::class, 'attribute')]marks parameters ofrender()only. The block must be a block of the same theme or plugin, the attribute marked with#[ProvidesContext], the parameter type that of the attribute (it may be nullable where the attribute is not), and the parameter needs a default or a nullable type.usesContextmust not name context of your own blocks; use#[Context]instead.