前面做的自訂區塊內容都寫死在區塊自己身上,像 service-card 就是 number、title、text 那幾個屬性塞不了別的東西,但很多時候你要的是一個「容器」:一張卡片裡面能讓使用者自由放段落、圖片、按鈕,甚至再放一組欄位,這種「區塊裡裝區塊」的能力,就是今天的主題:InnerBlocks。
先想個實際情境:做一個卡片區塊,一開始用屬性做:title、text、button,上線後客戶說「這張卡片我想多放一張圖」,你加一個 image 屬性;再過兩天「想放一個清單」,每天都要加一個新屬性你會發現自己在重造整個編輯器。
正確的思路是:卡片只負責「外框和樣式」,裡面放什麼交給使用者用核心區塊自由組合。卡片變成一個容器,它容納 group、columns、段落、圖片等任何區塊,這就是 InnerBlocks 要解決的事。
拿傳統主題類比,這很像 get_template_part() 裡那個 the_content(),外層是你控制的版型,中間留一塊「內容區」讓編輯者自由填,InnerBlocks 就是區塊版的「內容區」。
做一個 card 容器區塊,關鍵在 edit 和 save 都要標出「內部區塊放這裡」,寫法用 useInnerBlocksProps:
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
const TEMPLATE = [
[ 'core/heading', { level: 3, placeholder: '卡片標題' } ],
[ 'core/paragraph', { placeholder: '卡片內容…' } ],
];
edit() {
const blockProps = useBlockProps( { className: 'wp-block-block-theme-card' } );
const innerProps = useInnerBlocksProps( blockProps, {
template: TEMPLATE,
templateLock: false,
} );
return <div { ...innerProps } />;
}
save 對應輸出 InnerBlocks.Content(或 useInnerBlocksProps.save),讓內部區塊的內容存進去:
save() {
const blockProps = useBlockProps.save( { className: 'wp-block-block-theme-card' } );
const innerProps = useInnerBlocksProps.save( blockProps );
return <div { ...innerProps } />;
}
把這兩段拆開看。先看 edit:
TEMPLATE:預設要塞進容器的內部區塊。這裡放一個 h3 標題和一個段落,每個項目格式是 [ 區塊名, 屬性 ]。給使用者一個起點,不用面對空白容器。useBlockProps( { className: '…card' } ):產生外層 <div> 該有的 props(class、data 屬性…),並把我們自己的 wp-block-block-theme-card class 加上去。useInnerBlocksProps( blockProps, { template, templateLock } ):這是關鍵那一步。它吃進外層的 blockProps,回傳一組「合體」後的 props:既保留外框的屬性又包含使用者可以拖放內部區塊的那塊編輯區,template 給它預設區塊,templateLock: false 讓使用者能自由增刪。return <div { ...innerProps } />:只回傳一個 <div>,把 innerProps 疊加上去。因為 innerProps 裡已經含著「內部區塊區」,這一個 div 就同時是「有樣式的外框」和「裝其他區塊的容器」。再看 save,它是 edit 的「儲存版」:
useBlockProps.save(...):save 版本的外框 props,套在真正要寫進資料庫的 HTML 上。useInnerBlocksProps.save( blockProps ):save 版本,它回傳的 props,子內容就是「內部區塊存下來的 markup」,等同舊寫法的 <InnerBlocks.Content />。return <div { ...innerProps } />:把外框加上內部區塊的 HTML 一起寫進文章內容。有個地雷要特別注意:edit 和 save 產生的外層結構必須一致(一樣的 <div>、一樣的 className),否則就會踩到 Day 18 那個「區塊驗證失敗」。這也是為什麼上面兩段都用同一個 wp-block-block-theme-card class、都只包一層 <div>。
這樣一來,卡片就成了一個容器,使用者可以在裡面加任何區塊,完成結果如下:

InnerBlocks 有幾個參數決定容器的開放程度:
| 參數 | 作用 |
|---|---|
template |
預設塞好的區塊(給使用者一個起點,不用面對空白) |
templateLock |
false 自由增刪/"insert" 不能增刪只能改/"all" 完全鎖死 |
allowedBlocks |
白名單:只准放哪幾種區塊(例如卡片內只准放標題和段落) |
templateLock 和 allowedBlocks 是控制編輯體驗的關鍵。太自由的話使用者能在你精緻的卡片裡塞一個離題的 Query Loop,版面就亂了;鎖太死又會失去彈性,這是設計取捨的問題,還是要依據管理者的需求來進行設計。
一個常見疑問:用了 InnerBlocks,這區塊是靜態還是動態?兩者都行。像上面的例子,save 輸出 InnerBlocks.Content,內容存進文章,是靜態容器,但你也能做動態容器:save 回傳 null,在 render.php 裡用 $content 變數輸出內部區塊:
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php echo $content; // InnerBlocks 的內容. ?>
</div>
render.php 拿到的 $content 就是內部區塊渲染好的 HTML。這在你外框要吃 PHP 邏輯(例如依條件加 class)、內容又要自由編排時很有用。
InnerBlocks 的程式碼(useInnerBlocksProps 的 edit/save 對應)很容易寫錯,尤其 save 那邊漏了就會整個壞掉。可以這樣跟 AI 溝通:「做一個 card 容器區塊,用 InnerBlocks,預設塞一個 h3 和一個段落,只准放標題、段落、圖片、按鈕,不鎖增刪。」把 template、allowedBlocks、templateLock 的意圖講明,AI 就照著設。
你要 review 的是 save 有沒有正確輸出內部內容、白名單合不合理。
容器做出來了,但這也產生另一個問題:使用者到底該「在區塊裡就地編輯」,還是「到右側欄改屬性」?這兩種編輯體驗各有優劣,下一篇我們專門聊這個取捨。
文章目錄:https://oberonlai.blog/category/2026-ithome/