Building a header block in WordPress

I’m building a website with two different page title styles:

Standard page title
Fancy page title

I built this as a custom Header block so we can customize the page header directly in the Gutenberg block editor. The steps to implement this are:

  1. Create the custom block with Advanced Custom Fields.
  2. Remove the standard page title if this block is used.
  3. Move the block up to touch the site header when it’s the first block on the page.

Create the header block

I use Advanced Custom Fields to build custom blocks. Here’s a detailed guide on building custom blocks with ACF.

I registered my header block and set the render template to the /partials/block-header.php file in my theme.

acf_register_block_type( array(
	'name'				=> 'header',
	'title'				=> __( 'Header', 'core-functionality' ),
	'render_template'		=> 'partials/block-header.php',
	'category'			=> 'formatting',
	'icon'				=> 'schedule',
	'mode'				=> 'auto',
	'keywords'			=> array(),
));

I went to Custom Fields > Add New and created this field group:

Then in partials/block-header.php I added the markup for the header.

Take note of the first line as it took me a bit of time to figure it out. If you need access to any information about the current post (like the title), standard template tags like get_the_title() and get_the_ID() won’t work in the Gutenberg block editor, but will work on the frontend.

ACF dynamically loads the block using AJAX and passes the post ID as $_POST['post_id']. So we’ll use that in the backend, and get_the_ID() on the frontend to get the Post ID. We use this to build the default page title ( get_the_title( $post_id ) ).

// Post ID is in $_POST['post_id'] when rendering ACF block in Gutenberg
$post_id = get_the_ID() ? get_the_ID() : $_POST['post_id'];

$title = get_field( 'title' );
if( empty( $title  ) )
	$title = get_the_title( $post_id );

$content  = get_field( 'content' );

$image = get_field( 'image' );
if( empty( $image ) )
	$image = get_option( 'options_ea_default_header' );

echo '<div class="block-header alignfull">';
	echo '<div class="block-header__image">' . wp_get_attachment_image( $image, 'ea_header' ) . '</div>';
	echo '<div class="block-header__content">';
		echo '<h1>' . esc_html( $title ) . '</h1>';
		echo wpautop( $content );
	echo '</div>';
echo '</div>';

The new header block works great, but now there are two page titles. The next step is to remove the standard page title if this block is used.

Remove the standard page title

Before Gutenberg, I would have used Genesis Title Toggle to disable the standard page title. The next version of Genesis will include the title toggle functionality, but this still isn’t as user friendly as it could be.

A better approach is to automatically remove the standard title if this block is used. We should also remove the title if an h1 heading block is used. If a block has innerBlocks (ex: columns, group…), we’ll scan through those as well.

This function will let you know if the current post has an h1 block. Add it to your theme’s functions.php file, and change acf/header to the name of your heading block.

/**
 * Recursively searches content for h1 blocks.
 *
 * @link https://www.billerickson.net/building-a-header-block-in-wordpress/
 *
 * @param array $blocks
 * @return bool
 */
function be_has_h1_block( $blocks = array() ) {
	foreach ( $blocks as $block ) {

		if( ! isset( $block['blockName'] ) )
			continue;

		// Custom header block
		if( 'acf/header' === $block['blockName'] ) {
			return true;

		// Heading block
		} elseif( 'core/heading' === $block['blockName'] && isset( $block['attrs']['level'] ) && 1 === $block['attrs']['level'] ) {
			return true;

		// Scan inner blocks for headings
		} elseif( isset( $block['innerBlocks'] ) && !empty( $block['innerBlocks'] ) ) {
			$inner_h1 = be_has_h1_block( $block['innerBlocks'] );
			if( $inner_h1 )
				return true;
		}
	}

	return false;
}

If you’re building a Genesis theme, you can use this function to unhook the heading and .entry-header markup:

/**
 * Remove entry-title if h1 block used
 * @link https://www.billerickson.net/building-a-header-block-in-wordpress/
 */
function be_remove_entry_title() {
	if( ! ( is_singular() && function_exists( 'parse_blocks' ) ) )
		return;

	global $post;
	$blocks = parse_blocks( $post->post_content );
	$has_h1 = be_has_h1_block( $blocks );

	if( $has_h1 ) {
		remove_action( 'genesis_entry_header', 'genesis_entry_header_markup_open', 5 );
		remove_action( 'genesis_entry_header', 'genesis_entry_header_markup_close', 15 );
		remove_action( 'genesis_entry_header', 'genesis_do_post_title' );
	}
}
add_action( 'genesis_before_entry', 'be_remove_entry_title' );

If you’re building a custom theme, you can integrate the be_has_h1_block() into the function that displays the post title. For example, my EA Starter theme can be customized like so (here’s the original):

/**
 * Entry Title
 *
 */
function ea_entry_title() {

	global $post;
	$blocks = parse_blocks( $post->post_content );
	if( ! be_has_h1_block( $blocks ) )
		echo '<h1 class="entry-title">' . get_the_title() . '</h1>';
}
add_action( 'tha_entry_top', 'ea_entry_title' )

Move block up to touch site header

The .site-inner on this site has 16px of padding on mobile and 40px on tablet/desktop.

If the header block is the first block on the page, this CSS adds negative margin to the top of the block so it is flush with the header:

.entry-content  > .block-header:first-child {
	margin-top: -16px;
	@media only screen and (min-width: 768px) {
		margin-top: -40px;
	}
}

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Matt Whiteley says

    This is a great approach! I’ve created a variety of hero blocks and I’ve been using page templates to ensure the title is removed from any pages using the hero block which contains the H1. This removes that step from the process! Great stuff as always Bill.

  2. Angie Vale says

    Great post, what approach would you use for having a header block for a custom post archive page?

  3. I Hate Blocks says

    In a theme without the Block Editor this is so much simpler, why waste time on doing it this way?

    • Bill Erickson says

      How is it easier in the Classic Editor? Classic Editor doesn’t have a method of building custom header areas like this.

      In my opinion, this is an example of what makes the block editor shine. What would have required a custom metabox and disjointed content creation process in TinyMCE can now be implemented in an intuitive and easy-to-use way.

      You could also pair this with a default block template so new posts/pages automatically have the header block, which can be customized or removed.

  4. Martin Jost says

    Hey Bill,

    nice idea to put the header inside the block editor. But there is an unfriendly point for the user. In the backend, the title of the block editor is still displayed above the header block. Is there a way to place the header block inside the block editor itself above the page title?

    An other point is. How we can restrict the header block to use it only one time? The header could hav options like “full screen”. At this point it’s user unfriendly to have the possibility to use the header block multiple times.

    Thank you for your work.

    • Bill Erickson says

      You could use block templates to pre-populate the editor with the Header block, use CSS to hide the standard page title in the Gutenberg block editor, then hook into save_post to update the standard page title based on the content of the header block.

      To limit the block’s usage to once on the page, update the block registration to include 'multiple' => false in the supports parameter:

      acf_register_block_type( array(
      	'name'				=> 'header',
      	'title'				=> __( 'Header', 'core-functionality' ),
      	'render_template'		=> 'partials/block-header.php',
      	'category'			=> 'formatting',
      	'icon'				=> 'schedule',
      	'mode'				=> 'auto',
      	'keywords'			=> array(),
      	'supports'			=> array( 'multiple' => false ),
      ));
      
  5. Martin Jost says

    Thank you Bill. The “multiple” parameter is very helpful. But unfortunately one thing remains user unfriendly. You can move the block or insert it somewhere in the middle between other blocks. This is a bit confusing for the user. Or is there a way to hold the header block on the first position?

    • Bill Erickson says

      No, WordPress doesn’t currently let you lock one block in place and add new ones below it. You can use 'template_lock' => 'all' to prevent blocks from being re-ordered and new ones added, but that’s not what you want (can’t add new blocks).

  6. David says

    Hi Bill, works great! Except… my breadcrumbs (in this case, Yoast) are sitting above the header. Do you have any thoughts on how this can be implemented when breadcrumbs are enabled?

    • Bill Erickson says

      You could include the breadcrumbs in your custom block, and also include it at the top of the page if the header block is not present on the page.

      See the code snippet above for be_remove_entry_title(). You would use the same approach to remove your standard breadcrumbs if the header block is present.

  7. Zach Miller says

    Thanks Bill, I didn’t know how to access the page-title within Gutenberg, this really helped me out!

  8. Kasia Gawlak says

    $post_id = get_the_ID() ? get_the_ID() : $_POST['post_id'];

    Thank you a million times for this.