14+ years building on WordPress / Replies in under 5 hours
Page Builders & Theming 9 min read · Updated July 2026

Block Patterns vs. InnerBlocks: When to Use Each (and How to Build Both)

AK
Ajay Khandal
WordPress Developer
Beyond the Basics: Mastering Block Patterns and InnerBlocks for Scalable Layouts
TL;DR

Block Patterns and InnerBlocks are distinct tools that solve different problems. Block Patterns are collections of pre-configured blocks that editors insert and then customise freely — each inserted copy is independent (unsynced) or linked to all other copies (synced, via Synced Patterns introduced in WordPress 6.3). They're registered via register_block_pattern() in PHP or as .php files in a block theme's /patterns/ directory with a metadata header. InnerBlocks is a React component used inside a custom block's edit.js that creates a nested block container — it accepts template (default block structure), allowedBlocks (restricts which blocks can be added), and templateLock ("all" locks structure completely, "insert" allows reordering only, false allows full editor freedom). The modern useInnerBlocksProps() hook merges the block wrapper and InnerBlocks container into one element, eliminating an extra wrapper div in the front-end HTML. Decision rule: if editors need a flexible layout starting point they customise per page, use a Block Pattern (no custom block required, zero front-end JavaScript). If you need to programmatically enforce what goes inside a block, use InnerBlocks.

Two tools handle repeatable layouts in the WordPress block editor, and they’re often confused because they both involve groups of blocks: Block Patterns and InnerBlocks. They’re not alternatives — they solve different problems, and knowing which one fits your situation is the difference between an editor experience that feels natural and one that constantly fights you.

Block Patterns are for editors. InnerBlocks are for developers. Once that distinction is clear, the right choice in any given situation becomes obvious.

What Block Patterns Are

A Block Pattern is a predefined arrangement of blocks that editors can insert into any post or page with one click. A pricing table, a hero section, a two-column feature comparison, a testimonial grid — any layout you build from standard WordPress blocks can be saved as a pattern and made available in the block inserter.

The key property of a pattern: after insertion, each copy is independent. An editor inserts a pattern, edits the text in that instance, and the original pattern and every other inserted copy are unaffected. Patterns are templates, not live links.

This is distinct from Synced Patterns (called Reusable Blocks before WordPress 6.3). Synced Patterns do maintain a live link — editing one instance updates every instance sitewide. Use a Synced Pattern for content that must be identical everywhere: a disclaimer paragraph, a contact CTA block, a promotional banner. Use a regular (unsynced) pattern for layout templates where each instance gets unique content.

Registering Block Patterns in PHP

The standard way to register a pattern for a plugin or classic theme is register_block_pattern(), called on the init hook:

function my_theme_register_patterns() {
    register_block_pattern(
        'my-theme/hero-section',
        array(
            'title'       => __( 'Hero Section', 'my-theme' ),
            'description' => __( 'A full-width hero with heading, description, and CTA button.', 'my-theme' ),
            'categories'  => array( 'my-theme-layouts' ),
            'keywords'    => array( 'hero', 'banner', 'header' ),
            'content'     => '<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"6rem","bottom":"6rem"}}}} -->
<div class="wp-block-group alignfull">
  <!-- wp:heading {"level":1,"placeholder":"Hero headline"} -->
  <h1 class="wp-block-heading">Your Headline Here</h1>
  <!-- /wp:heading -->
  <!-- wp:paragraph {"placeholder":"Supporting text"} -->
  <p>A short sentence that supports the headline and explains what this page is about.</p>
  <!-- /wp:paragraph -->
  <!-- wp:buttons -->
  <div class="wp-block-buttons">
    <!-- wp:button -->
    <div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Get started</a></div>
    <!-- /wp:button -->
  </div>
  <!-- /wp:buttons -->
</div>
<!-- /wp:group -->',
        )
    );
}
add_action( 'init', 'my_theme_register_patterns' );

The content field is raw block comment markup — the same format WordPress saves to post_content. The easiest way to get this markup is to build your layout in the editor, then switch to the Code Editor view and copy the block markup directly.

To group patterns logically in the inserter, register a pattern category first:

register_block_pattern_category(
    'my-theme-layouts',
    array( 'label' => __( 'My Theme Layouts', 'my-theme' ) )
);

Theme Patterns in Block Themes (FSE)

In a block theme built for Full Site Editing, the recommended way to ship patterns is through the /patterns/ directory — no PHP registration needed. Each .php file in this directory is automatically registered as a pattern, using a metadata header at the top of the file:

<?php
/**
 * Title: Hero Section
 * Slug: my-theme/hero-section
 * Categories: featured, my-theme-layouts
 * Keywords: hero, banner
 * Block Types: core/template-part/header
 * Inserter: true
 */
?>
<!-- wp:group {"align":"full"} -->
<div class="wp-block-group alignfull">
  <!-- wp:heading {"level":1} -->
  <h1 class="wp-block-heading"><?php echo esc_html__( 'Your headline here', 'my-theme' ); ?></h1>
  <!-- /wp:heading -->
</div>
<!-- /wp:group -->

The Block Types header is particularly useful: it limits the pattern’s availability to specific block types, so a pattern designed for the header template part only appears when you’re editing that template part — not in every post inserter. This approach keeps the block theme’s pattern library organised and context-aware.

For building the full block theme structure that these patterns live inside, the guide to high-performance block themes in WordPress 6.5+ covers the complete directory structure.

What InnerBlocks Are

InnerBlocks is a React component used inside a custom block’s edit.js that creates a nested block container — a slot where editors can add other blocks. It’s how you build a custom “Card” block that lets editors put an image, heading, and paragraph inside; or a “Tabs” block where each tab contains whatever blocks the editor chooses.

The core difference from patterns: InnerBlocks gives you programmatic control over structure. You define which blocks are allowed, what the default structure is, and whether the structure can be changed at all — and those constraints are enforced by the editor UI, not just by convention.

InnerBlocks in a Custom Block

A minimal custom block using InnerBlocks has three pieces:

edit.js — the editor component:

import { InnerBlocks } from '@wordpress/block-editor';

const TEMPLATE = [
    [ 'core/image', { placeholder: 'Card image' } ],
    [ 'core/heading', { level: 3, placeholder: 'Card title' } ],
    [ 'core/paragraph', { placeholder: 'Card description...' } ],
];

export default function Edit() {
    return (
        <div { ...useBlockProps() }>
            <InnerBlocks
                template={ TEMPLATE }
                allowedBlocks={ [ 'core/image', 'core/heading', 'core/paragraph', 'core/button' ] }
                templateLock="insert"
            />
        </div>
    );
}

save.js — the front-end output:

import { InnerBlocks } from '@wordpress/block-editor';

export default function save() {
    return (
        <div { ...useBlockProps.save() }>
            <InnerBlocks.Content />
        </div>
    );
}

templateLock controls what editors can do with the template:

  • "all" — the template is completely locked: editors can edit the content of each block but cannot add, remove, or reorder blocks
  • "insert" — editors can reorder existing blocks but cannot add or remove them
  • false — no lock: the template provides defaults but editors have full freedom

useInnerBlocksProps: The Modern Approach

For blocks where the InnerBlocks container needs custom HTML attributes or CSS classes — or where the block wrapper and the InnerBlocks container need to be the same element — use the useInnerBlocksProps hook instead of the <InnerBlocks /> component:

import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

export default function Edit() {
    const blockProps = useBlockProps( { className: 'my-card' } );
    const innerBlocksProps = useInnerBlocksProps( blockProps, {
        template: TEMPLATE,
        allowedBlocks: [ 'core/image', 'core/heading', 'core/paragraph' ],
        templateLock: 'insert',
        orientation: 'vertical',
    } );

    return <div { ...innerBlocksProps } />;
}

The difference: with <InnerBlocks />, the block wrapper and the InnerBlocks container are separate elements (an extra wrapper div). With useInnerBlocksProps, they merge — the block wrapper IS the InnerBlocks container. This produces cleaner HTML on the front end by eliminating one layer of nesting.

The orientation option controls how keyboard navigation works between blocks in the editor and how the built-in appender appears: "horizontal" for flex-row layouts (like a button group), "vertical" (the default) for stacked layouts.

Block Patterns vs. InnerBlocks: When to Use Which

Situation Use Reason
Predefined layout editors insert and customise freely Block Pattern No custom block needed; pattern is decoupled on insertion
Content that must be identical across every instance Synced Pattern Live link between all instances; one edit updates all
Custom block that contains editor-chosen sub-blocks InnerBlocks Creates a nested block container inside the parent block
Block with a fixed structure editors can’t break InnerBlocks + templateLock: "all" Enforces the layout at the editor level, not just by convention
Complex functional block (tabs, accordion, slider) InnerBlocks + Interactivity API InnerBlocks handles layout; Interactivity API handles behaviour
FSE theme layout templates Theme pattern in /patterns/ Auto-registered by block theme, context-aware via Block Types header
Pattern shared between a plugin and the pattern directory register_block_pattern() PHP registration gives full control over availability and metadata

The most common mistake is reaching for a custom block with InnerBlocks when a Block Pattern would do. If editors just need a layout starting point they can freely modify — and you don’t need to programmatically control what goes inside — a pattern is significantly less code and has zero JavaScript overhead.

Combining Patterns and InnerBlocks

The two tools compose cleanly. A Block Pattern can include custom blocks that use InnerBlocks — the pattern provides the outer layout template (which editors can customise freely after insertion), while the InnerBlocks-based blocks inside it enforce structural constraints on their own content.

For example: a “Team Section” pattern might include three instances of a custom “Team Member Card” block. Each card uses InnerBlocks with templateLock: "insert" to ensure every card always has an image, a name heading, and a title paragraph — but the pattern itself is unsynced, so editors can adjust the section layout and card content freely per page.

For complex blocks that combine InnerBlocks with dynamic front-end behaviour (tabs where clicking a tab shows the right InnerBlocks panel, for example), the WordPress Interactivity API handles the JavaScript layer — using data-wp-on--click and context state rather than jQuery to toggle visibility between InnerBlocks panels.

Patterns in Headless WordPress

When WordPress is used headless — with a Next.js frontend consuming the REST API — Block Patterns are stored in post_content as block comment markup, which the REST API returns as raw HTML in the content.rendered field. The inserted pattern becomes part of the post content, no different from any other block. This means patterns require no special handling in a headless setup — they render as part of the standard content field.

For custom blocks using InnerBlocks in a headless context, the rendered HTML of the nested blocks is also included in content.rendered. Real-time content synchronisation between WordPress and a decoupled frontend can be handled via WordPress webhooks and Next.js revalidation, which triggers a rebuild of the affected pages whenever post content changes.

Performance Characteristics

Block Patterns have zero front-end overhead — once inserted, they’re standard block markup stored in post_content. There’s no runtime pattern code; the editor just uses the markup as a starting point. The rendered HTML is no different from blocks placed individually.

Custom blocks using InnerBlocks do require a small JavaScript bundle (the block’s editorScript) — but only in the editor. On the front end, save.js with <InnerBlocks.Content /> renders as static HTML with no runtime dependency. The front-end output of an InnerBlocks block is identical in performance to manually placed blocks; the JavaScript cost is editor-only.

For a complete guide to custom Gutenberg block development — registering block.json, writing edit.js and save.js, and using useBlockProps — the custom Gutenberg block guide covers the full workflow. If you’re building blocks as part of a bespoke theme, the custom theme development service covers the full scope — block registration, pattern library, FSE template structure, and Interactivity API integration.

Frequently asked questions

A Block Pattern is a predefined arrangement of existing blocks that editors can insert from the block inserter. Once inserted, the pattern becomes independent block markup — it's decoupled from the original pattern, so editors can modify each inserted instance freely without affecting others. InnerBlocks is a React component used inside a custom block's editor code that creates a container for other blocks. It's for custom blocks where you need control over what's nested inside — which blocks are allowed, what the default structure is, and whether editors can change the structure at all.

Unsynced Block Patterns (the default) work like templates: inserting one creates an independent copy. Editing one inserted instance doesn't affect the original pattern or any other inserted copy. Synced Patterns (called Reusable Blocks before WordPress 6.3) maintain a live link between all instances — editing one updates every place it's inserted sitewide. Use a Synced Pattern for content that must be identical everywhere (a legal disclaimer, a promotional CTA). Use an unsynced pattern for layout templates where each page needs different content.

templateLock controls how much editors can change the block structure defined in the template prop. "all" locks it completely — editors can change the content inside each block but cannot add, remove, or reorder blocks. "insert" allows reordering but not adding or removing. false (or not set) means the template is just a starting point — editors have full freedom to add, remove, and reorder blocks. Choose "all" for structured content where layout integrity matters (a card block that must always have image + heading + text in that order). Choose false for flexible containers where the template is just a suggestion.

useInnerBlocksProps is a React hook from @wordpress/block-editor that merges the block wrapper attributes and InnerBlocks props into a single set of HTML attributes. The result is that the block wrapper element and the InnerBlocks container are the same element — one fewer div in the rendered HTML. Use it when your block's wrapper div should also be the InnerBlocks container, which produces cleaner markup. The standard component creates two separate elements: the block wrapper (from useBlockProps) and the InnerBlocks container inside it. For most custom blocks, useInnerBlocksProps produces better front-end HTML.

Block Patterns are stored as block comment markup in post_content. When an editor inserts a pattern, that markup becomes part of the post's content, no different from any other blocks. The REST API returns this as rendered HTML in the content.rendered field. A headless frontend (Next.js, Nuxt, Astro) that consumes this field gets the fully rendered pattern HTML without any additional handling. Synced Patterns are stored as a reference block comment (wp:block with a ref ID) — the REST API resolves these to their rendered HTML by default, so headless frontends receive the same output either way.

Use a Block Pattern when editors need a flexible layout starting point they can modify freely per page — no custom block is required, there's no JavaScript overhead, and the content isn't structurally constrained. Use a custom block with InnerBlocks when you need to programmatically enforce what goes inside — which blocks are allowed, what the default layout is, or whether editors can break the structure. A practical rule: if you'd describe the feature as a layout template, use a Pattern. If you'd describe it as a component with defined slots (card with image slot + text slot), use InnerBlocks.

AK

Written by Ajay Khandal

WordPress Developer — building, fixing and speeding up WordPress sites.

Work with me →