Storing Block Patterns in HTML Files for Nicer Code [Technical]

Block patterns are an awesome feature in WordPress that let editors quickly insert complex combinations of blocks—even with preset settings—into the editor. I recently talked about them at the WordPress Seattle Developer Meetup, and I want to expand on one small code thing I showed in that presentation.

What Are Patterns?

With patterns, editors can quickly create attractive pages by inserting pre-designed placeholder content to customize. Here’s the pattern inserter showing two patterns I was working on today.

Block Patterns inserter in the WordPress block editor showing two large block patterns with background image and content.

These are full-screen banners that include the page title, a large intro paragraph, and an optional button, all in front of a few branded images.

Registering a Block Pattern

At its simplest, the register_block_pattern function only needs a namespaced slug, a title for the pattern, and the HTML content of the block(s) in the pattern. It’s also usually a good idea to include the viewportWidth attribute so that WordPress generates an attractive and realistic preview in the Pattern selection sidebar.

(Quick aside: If you’re like me and use Firefox as your primary browser, be aware that there’s an infuriating bug that hides Block Patterns previews in Firefox, seemingly at random.)

Here’s an extremely simple example:

namespace MRW\Theme;

add_action( 'after_setup_theme', 'MRW\Theme\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => '<!-- wp:paragraph --><p>A single paragraph block style</p><!-- /wp:paragraph -->',
			'viewportWidth' => 1200,
		)
	);
}

That example is so extremely simple in fact, that it’s not particularly realistic.

Block Patterns become increasingly useful as they get more complex (and would therefore require more work to recreate). However, that can make the 'content' property of the settings array start to look really… interesting.

Here’s a more realistic example:

namespace MRW\Theme;

add_action( 'after_setup_theme', 'MRW\Theme\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Banner', 'mrw' ),
			'content' => '<!-- wp:cover {"url":"https://mysite.local/wp-content/themes/my-theme/img/block-pattern-images/bubbles-cover-background.png","id":86,"hasParallax":true,"dimRatio":0,"minHeight":100,"minHeightUnit":"vh","contentPosition":"bottom left","align":"full","className":"cover-header"} --><div class="wp-block-cover alignfull has-parallax has-custom-content-position is-position-bottom-left cover-header" style="background-image:url(https://mysite.local/wp-content/themes/my-theme/img/block-pattern-images/bubbles-cover-background.png);min-height:100vh"><div class="wp-block-cover__inner-container"><!-- wp:post-title {"level":1,"align":"wide","textColor":"black","className":"is-style-brush-font"} /--><!-- wp:paragraph {"fontSize":"large"} --><p class="has-large-font-size">Summary of the page. Make it awesome and inspirational!</p><!-- /wp:paragraph --><!-- wp:buttons --><div class="wp-block-buttons"><!-- wp:button --><div class="wp-block-button"><a class="wp-block-button__link">Optional Call to Action Button</a></div><!-- /wp:button --></div><!-- /wp:buttons --></div></div><!-- /wp:cover -->',
			'viewportWidth' => 1200,

		)
	);
}

I do not like how that looks.

Storing Block Patterns in HTML Files

After getting sick of ugly code like that, I realized that I could store the HTML in a separate file. I added a block-patterns folder to my theme, put a my-block-pattern.html file with the pattern’s HTML in the folder, and then grabbed the HTML from the file with file_get_contents:

namespace MRW\Theme;

add_action( 'after_setup_theme', 'MRW\Theme\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/my-block-pattern.html' ) ),
			'viewportWidth' => 1200,

		)
	);
}

Dealing with Images / Dynamic File Paths

Using this technique brought calm and tidiness to my code editor but left me with one problem: the HTML is static. Why is that a problem? Look closely at the ugly block pattern code above and you’ll see it contains images that live in the theme’s folder.

Since I develop sites locally as most developers do, I need my block patterns to work regardless of where the site lives. It’s not uncommon for me to build a site locally (https://myclient.local), stage the site on my hosting (https://myclient.mrwweb.com), and then launch the site on the client’s domain. In the past I had just sucked it up and manually updated my block patterns containing file paths, but today I realized there was an easy solution staring me in the face:

  1. Define a “token” placeholder for the site path
  2. Do a find and replace for that string in my PHP code
namespace MRW\Theme;


function replace_theme_uri( $markup ) {
	return str_replace( '{{theme_uri}}', get_stylesheet_directory_uri(), $markup );
}

add_action( 'after_setup_theme', 'MRW\Theme\register_my_block_patterns' );
function register_my_block_patterns() {
	register_block_pattern(
		'mrw/my-block-pattern',
		array(
			'title' => __( 'My First Block Pattern', 'mrw' ),
			'content' => replace_theme_uri( file_get_contents( get_theme_file_path( 'block-patterns/my-block-patterns.html' ) ) ),
			'viewportWidth' => 1200,
		)
	);
}

I chose {{theme_uri}} as my placeholder token. Then I used the replace_theme_uri function to replace it with the path to the theme folder! 🎉

With that problem solved, I see no other downsides to this approach, and it opens up other interesting possibilities!

Bonus: Composite Patterns

One of the possibilities I’ve used in a real project already is reusing pattern code multiple times in different combinations.

For instance, imagine your site has a ‘Banner” pattern, a “Three-column Feature” pattern, and a “Gallery with Description” pattern. Once those are in separate files, they can be mixed-and-matched to create patterns that represent full-page layouts:


namespace MRW\Theme;

add_action( 'after_setup_theme', 'MRW\Theme\register_my_block_patterns' );
function register_my_block_patterns() {

	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Banner', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/banner.html' ) ),
			'viewportWidth' => 1200,

		)
	);

	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Three-Column Feature', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/three-column-feature.html' ) ),
			'viewportWidth' => 1200,

		)
	);

	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Gallery with Description', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/gallery-with-description.html' ) ),
			'viewportWidth' => 1200,

		)
	);

	register_block_pattern(
		'mrw/banner',
		array(
			'title' => __( 'Landing Page', 'mrw' ),
			'content' => file_get_contents( get_theme_file_path( 'block-patterns/banner.html' ) ) . file_get_contents( get_theme_file_path( 'block-patterns/three-column-feature.html' ) ) . file_get_contents( get_theme_file_path( 'block-patterns/gallery-with-description.html' ) ),
			'viewportWidth' => 1200,

		)
	);
}

That code registers four patterns but only references three HTML files! The fourth pattern, “Landing Page”, is a composite block pattern only using code from other patterns. Update an individual pattern and the composite pattern will get updated too!


I hope this saves a developer or two some time and headaches. Let me know if you’ve found ways to improve on this in any way!

Join the Discussion

This site uses Akismet to reduce spam. Learn how your comment data is processed.