Crash Course in Gutenberg Granular Block Development using Nested Block Structures

In order to best support the upcoming FSE (Full Site Editing) many plugin developers are taking the approach of creating granular blocks. These are blocks that enable full design control by breaking the UX rendered down into smaller pieces that the user can style individually. This approach is in contrast to creating monolithic blocks which mimic the existing approach of shipping a PHP/HTML mixed template or providing a shortcode that outputs an entire layout.

The first key to creating granular blocks is understanding how to make nested block structures. A nested block structure starts with a parent block. In Saber Commerce our block-based cart starts with a single block named Cart. This is the only block in the structure that is available in the block editor for insertion. All the child blocks within Cart are set to be hidden so they don’t clutter up the editor insert list. This is of course an optional design choice, because if your child blocks are useful outside of their parent then you may want to make them available for insertion. With sections like a shopping cart, the blocks are very specific to their intended purpose and either would not work at all (in the case of a dynamic cart block) or would not be useful in the case of various UX static blocks.

Inside of the parent Cart Block we have 4 nested child blocks. These are inserted automatically when the user inserts the parent Cart Block. This is done using the “template” option in the <InnerBlocks /> component.

const TEMPLATE = [
  [ 'saber-commerce/cart-header', {} ],
  [ 'saber-commerce/cart-table', {} ],
  [ 'saber-commerce/cart-totals', {} ],
  [ 'saber-commerce/cart-actions', {} ],

<InnerBlocks template={TEMPLATE} />

Note the code shown above goes into your “Block Edit Script”, which if you’re using the @wordpress/create-block from NPM is going to be in /src/edit.js. If you’re using another approach, this code might be in /src/index.js in the “edit” property passed to the registerBlockType() function.

In the Saber Commerce cart the block structure is 3-levels, so in addition to the parent/child relationship, there is also a child/grandchild. This structure is shown below. Only 1 of the child blocks contains child of it’s own, this is the Cart Table block. This structure allows for independent styling of the cart header and footer area where the Update Cart button lives.

  • Cart Block [ <InnerBlocks template={} templateLock={} /> ]
    • Cart Header Block [ No Children ]
    • Cart Table Block [ <InnerBlocks template={} templateLock={} /> ]
      • Cart Table Header Block [ No Children ]
      • Cart Item Row Block [ No Children ]
      • Cart Table Footer Block [ No Children ]
    • Cart Totals Block [ No Children ]
    • Cart Actions Block [ No Children ]

When you’re planning out a granular block structure a simple nested list like the one shown above can be a great place to start. It helps you visualize the structure and set in the place the parameters. Although the “template” and “templateLock” parameters are not shown above, you can probably imagine what they would be given the structure.

There are 3 parameters in InnerBlocks that you will yourself using over and over again so it’s worth understanding them in-depth and practicing with different configurations in order to understand how they work. Also worth noting here that the specifications for templateLock and possible others are still subject to change as well. Specifically proposals exist to make more fine-grained templateLock options available.

For those new nested blocks, the templateLock parameter enables you to restrict the user from changing the blocks nested inside of InnerBlocks. This enables us to make a block with a template, and either complete lock it down (using templateLock=”all”) or partially lock it so blocks can be moved but no new blocks can be inserted.

A very important concept is that templateLock is by default inherited by child blocks. Which means if we use templateLock=”all” on our parent Cart Block, then not only will the user be unable to change the direct child blocks (Cart Header, Cart Table etc.) but they will also be unable to change blocks contained inside them. So this would mean that by inheritance, the Cart Table block will have templateLock=”all” applied to it’s InnerBlocks whether we specify it or not. We can however, override this option. This is important in situations where you have a structure like the example below:

  • Parent Block [ InnerBlocks, template={CHILD_BLOCKS}, templateLocked=”all” ]
    • Child Block 1 [ No Children ]
    • Child Block 2 [ InnerBlocks, template={CHILD_BLOCKS} ] <<< Inherits templateLocked from Parent Block
    • Child Block 3 [ InnerBlocks, templateLocked={false} ] <<< Override the templateLocked to enable the user to insert a variety of content blocks.

Prevent Child Blocks from Appearing in the Gutenberg Inserter

When you create a nested block structure you may have anywhere from 3-5 or 10 or even 20+ blocks. Most of these, if not all, are not designed for regular insertion from the Gutenberg block selector. To prevent them from appearing on the list is simple, and you should do this to avoid cluttering the insertion menu. Bear in mind for instance that with our Saber Commerce block cart example, if you type /cart into the inserter text when it’s in block insertion mode, if we didn’t hide the child blocks there would be a grand total of 9 blocks found that match “cart”. And this would distract from inserting the parent block “Cart Block”.

If you’re using NPM package @wordpress/create-block then the majority of block settings are stored in block.json. In this JSON configuration for your block simply add to your block.json “supports” object the following – “inserter”: “false”.

If you’re not using the newer configuration format in block.json then put the “inserter” configuration into the array of settings (under “supports”) in the PHP block init callback.

Similar Posts