{"id":131719,"date":"2022-07-26T20:48:06","date_gmt":"2022-07-27T01:48:06","guid":{"rendered":"https:\/\/wpengine.com\/?post_type=resource&#038;p=131719"},"modified":"2023-10-26T14:44:14","modified_gmt":"2023-10-26T19:44:14","slug":"create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks","status":"publish","type":"resource","link":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/","title":{"rendered":"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>Don\u2019t Repeat Yourself<\/strong><\/h2>\n\n\n\n<p>For developers, laziness can be a virtue. A well-made, reusable component saves time and allows for efficient reuse and consistency. This lends itself well to creating editing experiences in WordPress, as consistency breeds familiarity and knowledge for those using a website on a day-to-day basis.<\/p>\n\n\n\n<p>WordPress made a lot of noise with the release of the Gutenberg block editor, now known simply as \u201cThe Editor.\u201d For content editing, drafting, and publishing, it provides an excellent experience, far better than older WYSIWYGs. As a developer, I want to provide the best editing experience for my clients, I want to leverage core functionality, and I don\u2019t want to reinvent the wheel. Luckily, WordPress lets us do all of those things with custom blocks.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Enter InnerBlocks<\/strong><\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/master\/packages\/block-editor\/src\/components\/inner-blocks\" target=\"_blank\" rel=\"noreferrer noopener\">InnerBlocks<\/a> is a great feature in the WordPress editor. Developers can use core blocks like the paragraph, headings, and buttons to create a consistent experience for a client. Instead of rewriting text sections and redeclaring fields, the client gets an experience that they become familiar with; editing and combining the same blocks. Let\u2019s take a look at a block we might need to build for a client, and see how InnerBlocks can help us achieve it.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Building a Block with InnerBlocks<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/wpengine.com\/wp-content\/uploads\/2022\/06\/unnamed-2.png\" alt=\"An example Call to Action Block with headline, paragraph, and button links\" class=\"wp-image-131720\" style=\"width:676px;height:237px\" width=\"676\" height=\"237\" \/><figcaption class=\"wp-element-caption\"><em>Figure: an example Call to Action Block. This includes a headline, paragraph, and some button links.<\/em><\/figcaption><\/figure>\n\n\n\n<p>Let\u2019s examine this block and consider how we might build it. A common tool we reach for when making blocks is <a href=\"https:\/\/www.advancedcustomfields.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Advanced Custom Fields<\/a> (ACF). <a href=\"https:\/\/www.advancedcustomfields.com\/resources\/blocks\/\" target=\"_blank\" rel=\"noreferrer noopener\">ACF Pro adds custom block support<\/a>, which lets developers write blocks in PHP for the editor and the front end. It also lets us use custom fields\u2014a paradigm many WordPress developers are familiar with. ACF supports InnerBlocks, meaning we can create a custom block, but won\u2019t even need to write custom code for that heading, paragraph, or buttons.&nbsp;<\/p>\n\n\n\n<p>With ACF Blocks, we can leverage core blocks to make a great editing experience for all the text we see in this block. Another potential option would be to use <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-patterns\/\" target=\"_blank\" rel=\"noreferrer noopener\">block patterns<\/a>, but as developers and curators of a fantastic WordPress experience, creating a custom block achieves the desired goal.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Block Registration Code<\/h3>\n\n\n\n<p>Here\u2019s the code to register our custom ACF block. Read more about using <a href=\"https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\" target=\"_blank\" rel=\"noreferrer noopener\">the acf_register_block_type function here<\/a>.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/**\n * Template for registering an ACF powered custom block.\n *\n * More info: https:\/\/www.advancedcustomfields.com\/resources\/blocks\/\n *\/\n\n\/**\n * Register the block.\n *\/\nadd_action(\n\t'acf\/init',\n\tfunction() {\n\t\t\/**\n\t\t * ACF block registration options here: https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\n\t\t *\/\n\t\tacf_register_block_type(\n\t\t\tarray(\n\t\t\t\t'name'            =&gt; 'call-to-action-demo', \/\/ JS will register as: acf\/{block-name}.\n\t\t\t\t'title'           =&gt; __( 'Call to Action ACF Demo', 'locale' ),\n\t\t\t\t'description'     =&gt; __( 'A custom ACF Call to Action block.', 'locale' ),\n\t\t\t\t'render_template' =&gt; 'partials\/blocks\/call-to-action-demo.php', \/\/ Change to block template.\n\t\t\t\t'category'        =&gt; 'design', \/\/ Category in the block inserter.\n\t\t\t\t'keywords'        =&gt; array( 'action', 'buttons', 'cta' ), \/\/ Searchable keywords.\n\t\t\t\t'supports'        =&gt; array(\n\t\t\t\t\t'align'  =&gt; false, \/\/ Disable support for align.\n\t\t\t\t\t'anchor' =&gt; true, \/\/ Enable support for anchor.\n\t\t\t\t\t'jsx'    =&gt; true, \/\/ Enable support for JSX.\n\t\t\t\t\t'mode'   =&gt; false, \/\/ Disable ACF block edit\/preview mode switching as we are only using InnerBlocks for editable content.\n\t\t\t\t),\n\t\t\t)\n\t\t);\n\t}\n);<\/code><\/pre>\n\n\n\n<p>Let\u2019s break the code down. We\u2019re using the <a href=\"https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\" target=\"_blank\" rel=\"noreferrer noopener\">acf_register_block_type function<\/a> to register our block, and we\u2019re passing an array of arguments that define our block and turn functionality on and off.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Notice we\u2019re following the recommended procedure to add a unique name for our block, as well as a human-readable title, and description. We point to a render template, where we\u2019ll set up our PHP template for the block later. We\u2019ve also added a category where we want our block to be grouped in the <a href=\"https:\/\/wordpress.org\/support\/article\/adding-a-new-block\/#what-is-the-inserter\" target=\"_blank\" rel=\"noreferrer noopener\">Block Inserter<\/a>.<\/li>\n\n\n\n<li>We\u2019ve got searchable keywords to make the block easier to search for in the inserter.<\/li>\n\n\n\n<li>We add a \u2018supports\u2019 array to enable functionality like setting an HTML anchor and we\u2019ve turned off alignment as this block will always sit in the center of the page.&nbsp;<\/li>\n\n\n\n<li>That supports array is where we turn on the InnerBlocks magic! Setting \u2018jsx\u2019 to true tells ACF that we will support rendering <a href=\"https:\/\/reactjs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">React<\/a> jsx templating inside our block.<\/li>\n<\/ul>\n\n\n\n<p>Let\u2019s review the content we need in our block:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Heading<\/li>\n\n\n\n<li>Paragraph text<\/li>\n\n\n\n<li>Buttons to take action!<\/li>\n<\/ul>\n\n\n\n<p>Now, normally we would assign fields to our block, as outlined here. But in this case, we don\u2019t need to\u2014we can leverage core blocks to achieve all of this. If the block needs an image background, for example, adding an ACF image field to the block could be a great way to achieve that. For now, though, let\u2019s stick to our InnerBlocks example and jump to the template.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Block Template<\/h3>\n\n\n\n<p>Here\u2019s the template for the new block. Make sure to point the block registration to the block template location. A common practice is putting these in a partials folder in the theme.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/**\n * ACF Call to Action example block template.\n *\n * More info: https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\n *\n * @param   array $block The block settings and attributes.\n * @param   string $content The block inner HTML (empty).\n * @param   bool $is_preview True during AJAX preview.\n * @param   (int|string) $post_id The post ID this block is saved to.\n *\n *\/\n\n\/\/ Create id attribute allowing for custom \"anchor\" value.\n$block_id = 'acf-call-to-action-block-demo-' . $block&#091;'id'];\nif ( ! empty( $block&#091;'anchor'] ) ) {\n\t$block_id = $block&#091;'anchor'];\n}\n\n\/\/ Create class attribute allowing for custom \"className\" and \"align\" values.\n$class_name = 'acf-call-to-action-demo';\nif ( ! empty( $block&#091;'className'] ) ) {\n\t$class_name .= ' ' . $block&#091;'className'];\n}\nif ( ! empty( $block&#091;'align'] ) ) {\n\t$class_name .= ' align' . $block&#091;'align'];\n}\n\n?&gt;\n\n\n&lt;div id=\"&lt;?php echo esc_attr( $block_id ); ?&gt;\" class=\"&lt;?php echo esc_html( $class_name ); ?&gt;\"&gt;\n\n\t&lt;div class=\"cta__inner\"&gt;\n\t\t\t&lt;?php\n\n\t\t\t\/\/ Set up innerBlocks and provide a template.\n\n\t\t\t\/\/ Restrict InnerBlocks to allowed block list.\n\t\t\t$allowed_blocks = array( 'core\/heading', 'core\/paragraph', 'core\/buttons' );\n\n\t\t\t\/\/ Start InnerBlocks with a template.\n\t\t\t$template = array(\n\t\t\t\tarray(\n\t\t\t\t\t'core\/heading',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'CTA Heading', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t\t'level'       =&gt; '2',\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tarray(\n\t\t\t\t\t'core\/paragraph',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'Add CTA text here', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tarray(\n\t\t\t\t\t'core\/buttons',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'Add CTA buttons here', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t),\n\t\t\t\t\tarray(\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t'core\/button',\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'text' =&gt; __( 'Take action', 'locale' ),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t'core\/button',\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'text' =&gt; __( 'Learn more', 'locale' ),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\t\/\/ Echo out our JSX InnerBlocks compoennt for the editor.\n\t\t\techo '&lt;InnerBlocks allowedBlocks=\"' . esc_attr( wp_json_encode( $allowed_blocks ) ) . '\" template=\"' . esc_attr( wp_json_encode( $template ) ) . '\" templateLock=\"false\" \/&gt;';\n\t\t\t?&gt;\n\t&lt;\/div&gt; \n\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p>Let\u2019s break this code down.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>At the start, we have a generic block template boilerplate that ACF provides in their block guidance, such as support for an anchor and custom class name.<\/li>\n\n\n\n<li>The InnerBlocks component can receive properties. We are using three in this template.\n<ul class=\"wp-block-list\">\n<li><strong>Allowed Blocks:<\/strong> We add an array of allowed blocks to curate. Only these blocks will be available for selection inside our custom block\u2019s InnerBlocks space.<\/li>\n\n\n\n<li><strong>Template: <\/strong>We can pass an array of blocks and we can pass block attributes for the blocks to start with when they first load into the editor.\n<ul class=\"wp-block-list\">\n<li>Notice that we can pass block attributes to set our users up for success. The heading has level 2 set already, and the paragraph is centered.<\/li>\n\n\n\n<li>Note: Core blocks will always be referred to in JavaScript as {plugin}\/blockname. In our case, we\u2019re using core blocks, but if you wanted to use a custom ACF block, you\u2019d write \u2018acf\u2019 in front of the block name. Remember, when using InnerBlocks, we are passing this component to the WordPress Editor, which uses React. That\u2019s why we\u2019re thinking in JS here.&nbsp;<\/li>\n\n\n\n<li>Another note: notice that the core\/buttons block uses InnerBlocks itself! We\u2019re actually passing an array of two core\/button blocks within!&nbsp;<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Template Locking: <\/strong>templateLock is set on the InnerBlocks component. If we set it to \u2018all\u2019, none of the blocks in our template provided to InnerBlocks could be moved or removed. If we set it to \u2018insert\u2019, the blocks within could only be moved around, no blocks could be removed, and no new blocks could be added. If we set it to \u2018false\u2019, the InnerBlocks instance will be unlocked and it will be unlocked regardless of any parent template locks (more on this further down). Read more about <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-templates\/\" target=\"_blank\" rel=\"noreferrer noopener\">Block Templates in the WordPress Block Editor Handbook<\/a>. We\u2019ll talk about block locking more.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>We have a new block! After all that, and some styling, of course, the example block&nbsp; looks like this in the editor:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/wpengine.com\/wp-content\/uploads\/2022\/06\/InnerBlocks3.png\" alt=\"The Call to Action Block with the heading block selected.\n\" class=\"wp-image-131721\" style=\"width:699px;height:245px\" width=\"699\" height=\"245\" \/><figcaption class=\"wp-element-caption\"><em>Figure: The Call to Action Block in the editor. The heading block is selected.<\/em><br><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Nesting Deeper: Curating the Experience Even More with Nested InnerBlocks<\/strong><\/h2>\n\n\n\n<p>We\u2019ve got a great block, and we didn\u2019t have to set up a WYSIWYG or even handle custom fields in our template. But what if we wanted to curate and fine-tune this experience even further for the client? Let\u2019s say we get a request that we ensure the heading is always present, so there\u2019s always a heading block, but the content after is flexible and could even include a list or another type of block. Our goal is to achieve that flexibility, while also preserving consistency.<\/p>\n\n\n\n<p>Let\u2019s consider some rules when working with InnerBlocks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A single block may only have one instance of InnerBlocks within.<\/li>\n\n\n\n<li>Multiple blocks inside InnerBlocks may use their own InnerBlocks components.<\/li>\n\n\n\n<li>InnerBlocks may be locked via the templateLock property, but instances of InnerBlocks deeper within a nested block structure may be unlocked again by setting templateLock to false.<\/li>\n<\/ul>\n\n\n\n<p>One solution is to create a new block that serves as a wrapper for InnerBlocks. We\u2019d include that block in our parent block template and lock it, but explicitly unlock our wrapper block within by setting \u2018templateLock\u2019 to false. We also may want to make sure this special wrapper block is only available within the parent block we choose, by <a href=\"https:\/\/developer.wordpress.org\/block-editor\/how-to-guides\/block-tutorial\/nested-blocks-inner-blocks\/#child-innerblocks-parent-and-ancestors\" target=\"_blank\" rel=\"noreferrer noopener\">setting a parent for our block<\/a>. We could allow multiple block types within that space to offer editors lists and more, while still only allowing the heading, buttons, and our wrapper block in the parent Call to Action block.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Individual Block Locking<\/strong><\/h2>\n\n\n\n<p>A new feature in WordPress 5.9 is the ability to <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-templates\/#individual-block-locking\" target=\"_blank\" rel=\"noreferrer noopener\">lock individual blocks in a template<\/a>. This is another possible solution to our flexible-yet-consistent problem.&nbsp;<\/p>\n\n\n\n<p>Here\u2019s what our template looks like with some individual blocks locked:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/**\n * ACF Call to Action example block template.\n *\n * More info: https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\n *\n * @param   array $block The block settings and attributes.\n * @param   string $content The block inner HTML (empty).\n * @param   bool $is_preview True during AJAX preview.\n * @param   (int|string) $post_id The post ID this block is saved to.\n *\n *\/\n\n\/\/ Create id attribute allowing for custom \"anchor\" value.\n$block_id = 'acf-call-to-action-block-demo-' . $block&#091;'id'];\nif ( ! empty( $block&#091;'anchor'] ) ) {\n\t$block_id = $block&#091;'anchor'];\n}\n\n\/\/ Create class attribute allowing for custom \"className\" and \"align\" values.\n$class_name = 'acf-call-to-action-demo';\nif ( ! empty( $block&#091;'className'] ) ) {\n\t$class_name .= ' ' . $block&#091;'className'];\n}\nif ( ! empty( $block&#091;'align'] ) ) {\n\t$class_name .= ' align' . $block&#091;'align'];\n}\n\n?&gt;\n\n\n&lt;div id=\"&lt;?php echo esc_attr( $block_id ); ?&gt;\" class=\"&lt;?php echo esc_html( $class_name ); ?&gt;\"&gt;\n\n\t&lt;div class=\"cta__inner\"&gt;\n\t\t\t&lt;?php\n\n\t\t\t\/\/ Set up innerBlocks and provide a template.\n\n\t\t\t\/\/ Restrict InnerBlocks to allowed block list.\n\t\t\t$allowed_blocks = array( 'core\/heading', 'core\/paragraph', 'core\/buttons' );\n\n\t\t\t\/\/ Start InnerBlocks with a template.\n\t\t\t$template = array(\n\t\t\t\tarray(\n\t\t\t\t\t'core\/heading',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'Heading', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t\t'level'       =&gt; '2',\n\t\t\t\t\t\t'lock'        =&gt; array(\n\t\t\t\t\t\t\t'move'   =&gt; true, \/\/ Block may nto be moved.\n\t\t\t\t\t\t\t'remove' =&gt; true, \/\/ Block may not be removed.\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tarray(\n\t\t\t\t\t'core\/paragraph',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'Add CTA text here', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tarray(\n\t\t\t\t\t'core\/buttons',\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'placeholder' =&gt; __( 'Add CTA buttons here', 'locale' ),\n\t\t\t\t\t\t'align'       =&gt; 'center',\n\t\t\t\t\t\t'lock'        =&gt; array(\n\t\t\t\t\t\t\t'move'   =&gt; true, \/\/ Block may not be moved.\n\t\t\t\t\t\t\t'remove' =&gt; true, \/\/ Block may not be removed.\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tarray(\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t'core\/button',\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'text' =&gt; __( 'Take action', 'locale' ),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t'core\/button',\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'text' =&gt; __( 'Learn more', 'locale' ),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\t\/\/ Echo out our JSX InnerBlocks compoennt for the editor.\n\t\t\techo '&lt;InnerBlocks allowedBlocks=\"' . esc_attr( wp_json_encode( $allowed_blocks ) ) . '\" template=\"' . esc_attr( wp_json_encode( $template ) ) . '\" \/&gt;';\n\t\t\t?&gt;\n\t&lt;\/div&gt; \n\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p>The heading and the buttons blocks should no longer be able to be moved or removed. Be sure to refresh the editor, remove, and then add the block again to get the templating changes.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Bonus: Making the Same Block Natively<\/strong><\/h2>\n\n\n\n<p>Once you have a build process in place, making native WordPress blocks with React is surprisingly easy and not too terribly different from templating out a block in php. Setting up a block development environment is outside the scope of this article, but there are a number of resources to get you started, such as the <a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/create-block\/\" target=\"_blank\" rel=\"noreferrer noopener\">WordPress Block Editor Handbook<\/a>. Once you\u2019re able to include custom blocks, you can quickly build blocks using InnerBlocks.<\/p>\n\n\n\n<p>Here is an example of our Call to Action block index.js in React. You\u2019ll see the same strategy we discussed above with ACF applied here.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { __ } from '@wordpress\/i18n';\nimport { registerBlockType } from '@wordpress\/blocks';\nimport { InnerBlocks } from '@wordpress\/block-editor';\nimport { useBlockProps } from '@wordpress\/block-editor';\n\n\/**\n * Block Name.\n * Create an example Call to Action Block\n * Uses InnerBlocks for editable content within.\n *\/\nexport const blockName = 'call-to-action';\n\n\/**\n * Block Config.\n * Set basic params for controlling the editor.\n *\/\nexport const BLOCK_CONFIG = {\n\t\/\/ Set up the block template.\n\tCTA_TEMPLATE: &#091;\n\t\t&#091;\n\t\t\t'core\/heading',\n\t\t\t{\n\t\t\t\tplaceholder: __('CTA Headline', 'locale'),\n\t\t\t\talign: 'center',\n\t\t\t\tlevel: 2,\n\t\t\t\tlock: {\n\t\t\t\t\tmove: true,\n\t\t\t\t\tremove: true,\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t&#091;\n\t\t\t'core\/paragraph',\n\t\t\t{\n\t\t\t\tplaceholder: 'Optional CTA text',\n\t\t\t\talign: 'center',\n\t\t\t\tlock: {\n\t\t\t\t\tmove: true,\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t&#091;\n\t\t\t'core\/buttons',\n\t\t\t{\n\t\t\t\tlock: {\n\t\t\t\t\tmove: true,\n\t\t\t\t\tremove: true,\n\t\t\t\t},\n\t\t\t\tclassName: 'is-content-justification-center',\n\t\t\t\talign: 'center',\n\t\t\t\t\/\/ __experimentalLayout - https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/block-library\/src\/buttons\/block.json\n\t\t\t\tlayout: {\n\t\t\t\t\ttype: 'flex',\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t},\n\t\t\t},\n\t\t\t&#091;\n\t\t\t\t&#091;\n\t\t\t\t\t'core\/button',\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: 'Apply now',\n\t\t\t\t\t\tlock: {\n\t\t\t\t\t\t\tmove: true,\n\t\t\t\t\t\t\tremove: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\t&#091;'core\/button', { text: 'Learn more' }],\n\t\t\t],\n\t\t],\n\t],\n\t\/\/ Set up the allowed blocks.\n\tALLOWED_BLOCKS: &#091;'core\/paragraph', 'core\/heading', 'core\/buttons'],\n};\n\n\/\/ Register the block via WP func. Change 'myplugin' to your plugin or theme.\nregisterBlockType(`myplugin\/${blockName}`, {\n\ttitle: __('Call to Action', 'locale'), \/\/ Change 'locale' to your locale for internationalization.\n\tdescription: __(\n\t\t'Call to action block with headline and buttons',\n\t\t'locale'\n\t),\n\tkeywords: &#091;__('call'), __('action'), __('cta')],\n\tcategory: 'design',\n\tsupports: {\n\t\tanchor: true,\n\t\tdefaultStylePicker: false,\n\t\thtml: false,\n\t\talign: false,\n\t},\n\tattributes: {\n\t\tanchor: {\n\t\t\ttype: 'string',\n\t\t\tdefault: '',\n\t\t},\n\t},\n\ttransforms: {},\n\tvariations: &#091;],\n\tedit: (props) =&gt; {\n\t\tconst blockProps = useBlockProps({\n\t\t\tclassName: `wp-block-myplugin-${blockName}`,\n\t\t});\n\n\t\treturn (\n\t\t\t&lt;div {...blockProps}&gt;\n\t\t\t\t&lt;div className=\"cta__inner\"&gt;\n\t\t\t\t\t&lt;div className=\"cta__inner-blocks-wrapper\"&gt;\n\t\t\t\t\t\t&lt;InnerBlocks\n\t\t\t\t\t\t\ttemplate={BLOCK_CONFIG.CTA_TEMPLATE}\n\t\t\t\t\t\t\tallowedBlocks={BLOCK_CONFIG.ALLOWED_BLOCKS}\n\t\t\t\t\t\t\trenderAppender={false}\n\t\t\t\t\t\t\/&gt;\n\t\t\t\t\t&lt;\/div&gt;\n\t\t\t\t&lt;\/div&gt;\n\t\t\t&lt;\/div&gt;\n\t\t);\n\t},\n\tsave: () =&gt; {\n\t\treturn (\n\t\t\t&lt;div&gt;\n\t\t\t\t&lt;div className=\"cta__inner\"&gt;\n\t\t\t\t\t&lt;InnerBlocks.Content \/&gt;\n\t\t\t\t&lt;\/div&gt;\n\t\t\t&lt;\/div&gt;\n\t\t);\n\t},\n});<\/code><\/pre>\n\n\n\n<p>Go forth and build! And to ensure you&#8217;re getting the best <a href=\"https:\/\/wpengine.com\/wordpress-hosting\/\" target=\"_blank\" rel=\"noreferrer noopener\">WordPress hosting<\/a> experience, make sure to check out <a href=\"https:\/\/wpengine.com\/managed-wordpress-hosting\/\" target=\"_blank\" rel=\"noreferrer noopener\">WP Engine&#8217;s managed hosting<\/a> solutions. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Additional Resources<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.billerickson.net\/innerblocks-with-acf-blocks\/\" target=\"_blank\" rel=\"noreferrer noopener\">Bill Erickson\u2019s article on InnerBlocks<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.billerickson.net\/category\/gutenberg-block-editor\/\" target=\"_blank\" rel=\"noreferrer noopener\">Bill Erickson\u2019s articles on the Block Editor<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.advancedcustomfields.com\/resources\/blocks\/\" target=\"_blank\" rel=\"noreferrer noopener\">ACF Blocks Guide<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.advancedcustomfields.com\/resources\/acf_register_block_type\/\" target=\"_blank\" rel=\"noreferrer noopener\">ACF Register Block Type Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/\" target=\"_blank\" rel=\"noreferrer noopener\">WordPress Block Editor Handbook<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/create-block\/\" target=\"_blank\" rel=\"noreferrer noopener\">WordPress Block Editor Handbook: Create a Block Tutorial<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Don\u2019t Repeat Yourself For developers, laziness can be a virtue. A well-made, reusable component saves time and allows for efficient reuse and consistency. This lends itself well to creating editing experiences in WordPress, as consistency breeds familiarity and knowledge for those using a website on a day-to-day basis. WordPress made a lot of noise with<span class=\"tile__ellipses\">&hellip;<\/span><span class=\"tile__ellipses--animated\"><\/span><\/p>\n","protected":false},"author":1,"featured_media":131722,"template":"","resource-topic":[901],"resource-role":[896,1296],"resource-type":[916],"class_list":["post-131719","resource","type-resource","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks<\/title>\n<meta name=\"description\" content=\"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.\" \/>\n<meta name=\"robots\" content=\"noindex, follow\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks\" \/>\n<meta property=\"og:description\" content=\"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/\" \/>\n<meta property=\"og:site_name\" content=\"WP Engine\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/wpengine\" \/>\n<meta property=\"article:modified_time\" content=\"2023-10-26T19:44:14+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/wpengine.com\/case-studies\/wp-content\/uploads\/2022\/06\/WP-Editing_1100x500.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1100\" \/>\n\t<meta property=\"og:image:height\" content=\"500\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@wpengine\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/\",\"url\":\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/\",\"name\":\"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks\",\"isPartOf\":{\"@id\":\"https:\/\/wpengine.com\/case-studies\/#website\"},\"datePublished\":\"2022-07-27T01:48:06+00:00\",\"dateModified\":\"2023-10-26T19:44:14+00:00\",\"description\":\"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.\",\"breadcrumb\":{\"@id\":\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/wpengine.com\/case-studies\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Resources\",\"item\":\"https:\/\/wpengine.com\/case-studies\/resources\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/wpengine.com\/case-studies\/#website\",\"url\":\"https:\/\/wpengine.com\/case-studies\/\",\"name\":\"WP Engine\",\"description\":\"Managed Hosting for WordPress\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/wpengine.com\/case-studies\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/wpengine.com\/case-studies\/#\/schema\/person\/f5301455463371a10d1fc290e9ad0085\",\"name\":\"WP Engine\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/wpengine.com\/case-studies\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d8770fe9625ca7c4601f13d9d0ab86565a6dac8cd6a77bfe2ada6d83c6837870?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d8770fe9625ca7c4601f13d9d0ab86565a6dac8cd6a77bfe2ada6d83c6837870?s=96&d=mm&r=g\",\"caption\":\"WP Engine\"},\"sameAs\":[\"https:\/\/wpengine.com\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks","description":"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.","robots":{"index":"noindex","follow":"follow"},"og_locale":"en_US","og_type":"article","og_title":"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks","og_description":"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.","og_url":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/","og_site_name":"WP Engine","article_publisher":"https:\/\/www.facebook.com\/wpengine","article_modified_time":"2023-10-26T19:44:14+00:00","og_image":[{"width":1100,"height":500,"url":"https:\/\/wpengine.com\/case-studies\/wp-content\/uploads\/2022\/06\/WP-Editing_1100x500.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_site":"@wpengine","twitter_misc":{"Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/","url":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/","name":"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks","isPartOf":{"@id":"https:\/\/wpengine.com\/case-studies\/#website"},"datePublished":"2022-07-27T01:48:06+00:00","dateModified":"2023-10-26T19:44:14+00:00","description":"InnerBlocks is a great feature in the WordPress editor that lets developers use core Gutenberg blocks to create a consistent experience for a client.","breadcrumb":{"@id":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/case-studies\/resources\/create-great-wordpress-editing-experience-save-time-gutenberg-innerblocks\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/case-studies\/"},{"@type":"ListItem","position":2,"name":"Resources","item":"https:\/\/wpengine.com\/case-studies\/resources\/"},{"@type":"ListItem","position":3,"name":"Create a Great WordPress Editing Experience and Save Time with Gutenberg InnerBlocks"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/case-studies\/#website","url":"https:\/\/wpengine.com\/case-studies\/","name":"WP Engine","description":"Managed Hosting for WordPress","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/case-studies\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/wpengine.com\/case-studies\/#\/schema\/person\/f5301455463371a10d1fc290e9ad0085","name":"WP Engine","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/case-studies\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d8770fe9625ca7c4601f13d9d0ab86565a6dac8cd6a77bfe2ada6d83c6837870?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d8770fe9625ca7c4601f13d9d0ab86565a6dac8cd6a77bfe2ada6d83c6837870?s=96&d=mm&r=g","caption":"WP Engine"},"sameAs":["https:\/\/wpengine.com"]}]}},"acf":[],"grid_image_url":"https:\/\/wpengine.com\/case-studies\/wp-content\/uploads\/2022\/06\/WP-Editing_343x245.png","media-type":{"term_id":916,"name":"Article","slug":"article"},"role":"<strong>Roles:<\/strong> Developer, Publisher","topic":"<strong>Topics:<\/strong> WordPress","_links":{"self":[{"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/resource\/131719","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/resource"}],"about":[{"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/types\/resource"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/users\/1"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/media\/131722"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/media?parent=131719"}],"wp:term":[{"taxonomy":"resource-topic","embeddable":true,"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/resource-topic?post=131719"},{"taxonomy":"resource-role","embeddable":true,"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/resource-role?post=131719"},{"taxonomy":"resource-type","embeddable":true,"href":"https:\/\/wpengine.com\/case-studies\/wp-json\/wp\/v2\/resource-type?post=131719"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}