title: >- Block Composition (useInnerBlocksProps), Block Hooks API, and Synced Patterns with Overrides description: >- Compound container architecture and design systems in WordPress 7.0+ (PHP 8.4+, TypeScript 5.8): seamless DOM merging via useInnerBlocksProps, structural locking with templateLock ('contentOnly'), automatic block injection via Block Hooks API, and synced patterns with instance overrides. module: gutenberg-blocks order: 4 difficulty: advanced estimatedTime: 40 min topics:
Attempting to construct an enterprise web platform using monolithic, atomic blocks (such as a single "Pricing Grid" block sporting 45 flat attributes in block.json: plan1Title, plan1Price, plan1Feature1, plan2Title...) rapidly devolves into an unmaintainable nightmare. Instead of bloating individual blocks with dozens of fields, modern Gutenberg architecture in WordPress 7.0+ ("Armstrong") is built upon Hierarchical Node Composition (Compound Block Pattern).
A complex interface section decomposes into a Parent Container (agentic-lab/bento-grid) and Strictly Constrained Child Blocks (agentic-lab/bento-cell). In the 2026 standard, three historic design flaws of early Gutenberg have been decisively eliminated:
<InnerBlocks /> component injected intermediate div.block-editor-inner-blocks > div.block-editor-block-list__layout wrappers between parent and children, causing CSS Grid and Flexbox layouts to collapse in the admin view. The modern useInnerBlocksProps() hook merges container properties (useBlockProps) and the child node list directly into a single, pristine semantic HTML tag (<ul {...innerBlocksProps}>).templateLock: "contentOnly" alongside Synced Pattern Overrides (core/pattern-overrides) allows teams to globally lock the grid, spacing, and design of a section in one central location, granting content managers access strictly to editing text headings, media assets, and link targets.hooked_blocks) allows a plugin to declaratively "hook" its block (e.g., an author bio, reading time badge, or verification indicator) directly after core/post-title or inside core/navigation across all block themes without touching the database.Think of block composition in WordPress 7.0+ as a modular server rack chassis:
useInnerBlocksProps) provides the structural frame (CSS Grid), power distribution (providesContext), and admittance rules: its bays physically accept only certified server blades (allowedBlocks: ['agentic-lab/bento-cell']), while the blades cannot sit orphaned on the floor ("parent": ["agentic-lab/bento-grid"] in the child's block.json).templateLock: "contentOnly") locks the cabinet door: operators interact strictly with front-panel diagnostic screens, unable to rip out chassis blades or alter rack order.Block Hooks API) automatically snaps a telemetry probe onto every new chassis rolling off the assembly line as soon as it powers on.┌─────────────────────────────────────────────────────────────────────────┐
│ PARENT: agentic-lab/bento-grid (useInnerBlocksProps -> <div.grid>) │
│ • providesContext: { "agentic-lab/gridColumns": 3 } │
│ • allowedBlocks: ["agentic-lab/bento-cell"] │
│ • templateLock: "contentOnly" (or "insert" / false) │
│ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ CHILD: agentic-lab/bento-cell │ │ CHILD: agentic-lab/bento-cell│ │
│ │ • parent: ["agentic/bento-grid"]│ │ • usesContext: │ │
│ │ • Direct CSS Grid Descendant! │ │ ["agentic/gridColumns"] │ │
│ └─────────────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘The hook merges useBlockProps and the child container into a single DOM element, guaranteeing 100% CSS Grid parity between editor and frontend.
The core validates insertion hierarchy: child blocks appear in the inserter only when focus resides inside an authorized ancestor.
contentOnly mode hides technical wrapper blocks from the list view, restricting editor interaction to designated text and media slots.
During template assembly, the core injects hooked blocks before/after anchor blocks (such as immediately after core/post-content).
Across Fullstack Agentic Lab enterprise systems, the combination of useInnerBlocksProps, Block Hooks API, and Synced Patterns is utilized for:
useInnerBlocksProps, bento-cell cards render as direct children of <div class="agentic-bento-grid"> in both the editor and the frontend, enabling grid-column: span 2 without hacking Gutenberg's DOM.Block Hooks API): injecting a "Reading Time & AI Article Summary" block directly after core/post-title across all single.html templates via a single block.json declaration and a PHP 8.4+ filter.register_block_pattern) locked with templateLock: "contentOnly" and Synced Pattern Overrides to editorial staff.Comparing legacy child block implementations via <InnerBlocks /> against modern WordPress 7.0+ composition:
Below is an enterprise-grade production artifact for WordPress 7.0+ (TypeScript 5.8 + PHP 8.4+). Part 1 implements a Bento Grid container using useInnerBlocksProps() with horizontal orientation and contentOnly locking. Part 2 implements a server-side Block Hooks API injector and an enterprise pattern featuring Pattern Overrides (core/pattern-overrides).
/**
* PART 1: Bento Grid Container via useInnerBlocksProps (TypeScript 5.8)
* File: src/blocks/bento-grid/index.tsx
*/
import { registerBlockType, type TemplateArray } from '@wordpress/blocks';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
interface BentoGridAttributes {
columns: number;
lockMode: '' | 'all' | 'insert' | 'contentOnly';
}
// Restrict child insertion strictly to verified Bento Cell blocks
const ALLOWED_CHILD_BLOCKS = ['agentic-lab/bento-cell'];
// Starter template populated when the grid is inserted into a post
const DEFAULT_BENTO_TEMPLATE: TemplateArray = [
[
'agentic-lab/bento-cell',
{
spanColumns: 2,
badge: 'WASM RUNTIME',
heading: 'PHP 8.4 Executed Natively in the Browser',
summary: 'Isolated client-side WordPress runtime with zero server RTT.',
},
],
[
'agentic-lab/bento-cell',
{
spanColumns: 1,
badge: 'DUAL-LLM GATEWAY',
heading: 'Gemini 2.5 + Claude 3.5',
summary: 'Strict Zod contract validation on all streaming responses.',
},
],
[
'agentic-lab/bento-cell',
{
spanColumns: 3,
badge: 'EDGE ISR',
heading: 'Instant Cache Invalidation via HMAC-SHA256 Webhooks',
summary: 'Global P95 response latency < 18ms across edge networks.',
},
],
];
registerBlockType<BentoGridAttributes>('agentic-lab/bento-grid', {
apiVersion: 3,
title: __('Agentic Bento Architecture Grid', 'agentic-lab'),
category: 'agentic-lab-architecture',
icon: 'grid-view',
edit({ attributes, setAttributes }) {
const { columns, lockMode } = attributes;
// 1. Configure parent CSS Grid node properties
const blockProps = useBlockProps({
className: `agentic-bento-grid has-${columns}-columns`,
style: {
display: 'grid',
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
gap: '1.5rem',
},
});
// 2. Merge InnerBlocks directly into our <section> without extra <div> wrappers!
const innerBlocksProps = useInnerBlocksProps(blockProps, {
allowedBlocks: ALLOWED_CHILD_BLOCKS,
template: DEFAULT_BENTO_TEMPLATE,
templateLock: lockMode === '' ? false : lockMode,
orientation: 'horizontal', // Horizontal movement arrows for grids
});
return (
<>
<InspectorControls>
<PanelBody title={__('Bento Grid Architecture', 'agentic-lab')} initialOpen={true}>
<RangeControl
label={__('Grid Column Count', 'agentic-lab')}
value={columns}
min={1}
max={4}
onChange={(val) => setAttributes({ columns: val ?? 3 })}
/>
<SelectControl
label={__('Layout Lock Mode (templateLock)', 'agentic-lab')}
value={lockMode}
options={[
{ label: 'Content Only (Text & Media Only — Recommended)', value: 'contentOnly' },
{ label: 'Lock Insert (Allow Reordering Only)', value: 'insert' },
{ label: 'Lock All (Complete Structural Lock)', value: 'all' },
{ label: 'Unlocked (Full Authoring Freedom)', value: '' },
]}
onChange={(val) =>
setAttributes({ lockMode: val as BentoGridAttributes['lockMode'] })
}
/>
</PanelBody>
</InspectorControls>
{/* Semantic <section> whose DIRECT children are the bento-cell elements */}
<section {...innerBlocksProps} />
</>
);
},
save({ attributes }) {
const { columns } = attributes;
const blockProps = useBlockProps.save({
className: `agentic-bento-grid has-${columns}-columns`,
style: {
display: 'grid',
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
gap: '1.5rem',
},
});
// Seamless child serialization inside <section>
const innerBlocksProps = useInnerBlocksProps.save(blockProps);
return <section {...innerBlocksProps} />;
},
});
/*
* ============================================================================
* PART 2: WordPress 7.0+ (PHP 8.4+) — Block Hooks API & Synced Pattern Overrides
* File: src/Blocks/BlockHooksAndPatternsOrchestrator.php
* ============================================================================
*
* <?php
* declare(strict_types=1);
*
* namespace AgenticLab\Blocks;
*
* use WP_Block_Template;
*
* final readonly class BlockHooksAndPatternsOrchestrator
* {
* public function registerHooks(): void
* {
* // 1. Auto-inject telemetry block immediately after post title across all FSE templates
* add_filter('hooked_block_types', $this->injectTelemetryBadgeAfterTitle(...), 10, 4);
*
* // 2. Configure default attributes on auto-injected hooked blocks
* add_filter('hooked_block_agentic-lab/telemetry-card', $this->configureHookedBlock(...), 10, 5);
*
* // 3. Register enterprise pattern with Pattern Overrides (core/pattern-overrides)
* add_action('init', $this->registerEnterprisePatterns(...));
* }
*
* public function injectTelemetryBadgeAfterTitle(
* array $hookedBlocks,
* string $position,
* string $anchorBlock,
* WP_Block_Template|array $context
* ): array {
* if ($anchorBlock === 'core/post-title' && $position === 'after') {
* $hookedBlocks[] = 'agentic-lab/telemetry-card';
* }
* return $hookedBlocks;
* }
*
* public function configureHookedBlock(
* ?array $parsedHookedBlock,
* string $hookedBlockType,
* string $relativePosition,
* array $parsedAnchorBlock,
* WP_Block_Template|array $context
* ): ?array {
* if ($parsedHookedBlock === null) {
* return null;
* }
* $parsedHookedBlock['attrs']['statusLevel'] = 'optimal';
* return $parsedHookedBlock;
* }
*
* public function registerEnterprisePatterns(): void
* {
* register_block_pattern('agentic-lab/verified-architecture-hero', [
* 'title' => 'Fullstack Agentic Lab — Hero with Override Slots',
* 'categories' => ['featured', 'banner'],
* 'content' => '<!-- wp:group {"templateLock":"contentOnly","layout":{"type":"constrained"}} -->
* <div class="wp-block-group">
* <!-- wp:heading {"metadata":{"name":"Hero Title","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
* <h2 class="wp-block-heading">Reference System Architecture</h2>
* <!-- /wp:heading -->
* <!-- wp:paragraph {"metadata":{"name":"Hero Summary","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
* <p>Engineering solution description with site-wide design synchronization.</p>
* <!-- /wp:paragraph -->
* </div>
* <!-- /wp:group -->',
* ]);
* }
* }
*/
Declare providesContext in the parent block.json, and declare parent: ['namespace/container'] and usesContext in the child block.json.
Merge useBlockProps and useInnerBlocksProps on a single semantic tag, configuring allowedBlocks, template, and orientation.
Activate contentOnly mode or Pattern Overrides (metadata.bindings.content -> core/pattern-overrides) for safe editorial handoff.
If the component should appear across block theme templates automatically, register it via hooked_blocks in block.json.