I’m building a website with two different page title styles:
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:
- Create the custom block with Advanced Custom Fields.
- Remove the standard page title if this block is used.
- 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;
}
}
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.
Angie Vale says
Great post, what approach would you use for having a header block for a custom post archive page?
Bill Erickson says
If it was just a header I would probably manually build it in the CPT archive template file. But if I needed the flexibility to add many blocks to the top of the CPT archive, I’d use this approach: https://www.billerickson.net/wordpress-gutenberg-block-widget-areas/
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.
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 thesupports
parameter: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).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.James says
Thanks for sharing this tutorial. Can you also please guide me how can I create a block template? I am doing the same using this resource https://wpitech.com/create-wordpress-gutenberg-block-templates/ but it’s giving an error and I am having some programming lines in front end. This is the code: https://gist.github.com/billerickson/cfea79cd8fb43c34f01fdd91509df9b3
Bill Erickson says
Here’s my Guide to block templates with Gutenberg
The code you provided is incomplete. It looks like you only copied half of the code from the site you linked to.
Peninah says
Just used this today as a guide. Thanks so much!
Zach Miller says
Thanks Bill, I didn’t know how to access the page-title within Gutenberg, this really helped me out!
Kasia Gawlak says
$post_id = get_the_ID() ? get_the_ID() : $_POST['post_id'];
Thank you a million times for this.