Using render_block to modify heading, paragraph, and columns block in Gutenberg editor

09 Jul 2020 , 4607 words

Recently I have started a pet project to create a portfolio site for Xuan. After a brief research I decide to pick WordPress to build the site, for its great Gutenberg editor introduced in version 5.0. All I need to do is to create a theme (from scratch, since Xuan’s requirement as an UI designer can be quite specific), and she can easily add her design works and blog posts to the site.

Wrap heading and paragraph tags in div

The way how themes and Gutenberg editor work together is that: when a post is created by user using the editor, each block is rendered as a <div>. Usually the <div> takes the class of form wp-block-xxx, so that a theme developer can add style for specific kinds of blocks.

However, that’s not the case for every block. Specifically, heading blocks are just rendered as <h1>, <h2>, … and paragraph blocks are just rendered as <p>, without a <div> wrapper. While I’m sure there is some good reasoning behind this design, it makes the styling tricky in my case. I want the headings and paragraphs wrapped in a <div> with a class like wp-block-heading and wp-block-paragraph, just like other blocks.

Luckily, this can be done with the render_block hook.

In the themes functions.php (if it’s not there, create the file under the theme folder), simply add this code:

function wrap_heading_paragraph_in_block($block_content, $block)
{
  if ($block['blockName'] === 'core/heading') {
    $content = '<div class="wp-block-heading">';
    $content .= $block_content;
    $content .= '</div>';
    return $content;
  }

  if ($block['blockName'] === 'core/paragraph') {
    $content = '<div class="wp-block-paragraph">';
    $content .= $block_content;
    $content .= '</div>';
    return $content;
  }

  return $block_content;
}

add_filter('render_block', 'wrap_heading_paragraph_in_block', 10, 2);

What does the code do? When a block is rendered, the wrap_heading_paragraph_in_block function is called, with the block_content and block object passed in as arguments.

In the function, the code tests if the block is a heading or paragraph block by inspecting its block name attribute. If it is either of them, it adds a <div> html wrapper around its current content (which is simply a <h1>, <h2>… or <p>). Otherwise it does nothing and just returns its original content.

Now if you refresh the page and inspect the html, the <h2> text </h2> becomes <div class="wp-block-heading"><h2> text </h2></div>, and the <p> text </p> becomes <div class="wp-block-paragraph"><p>text</p></div>.

Add back has-x-columns class to wp-block-columns

I think where Gutenberg editor really shines is to deal with multiple columns. If you create a columns block, the editor will let you choose the horizontal ratio between the columns, e.g., 1:1, 2:1, 1:1:1…

In my case, I need to create different styles for 2-column and 3-column blocks. But how can we tell the number of inner columns contained in the wp-block-columns block?

There was a time when Guttenberge editor used a has-x-columns class to give us the information. But it has been removed in WordPress 5.3 (see the Column block classnames section in the release note). Again, while there’s a good reason the team did so, it makes things a little trickier.

Once again, our friend render_block comes to rescue. The idea is similar to the previous problem’s solution: given a block object and its content, we can inspect the block’s attributes and modify the content if necessary. This time we want to find the number of inner blocks (which is the number of the inner columns), and insert that to the div’s class in the block content:

function add_class_to_wp_block_columns($block_content, $block)
{
  if ($block['blockName'] === 'core/columns') {
    $cls = 'wp-block-columns';
    $new_cls = ' has-' . sizeof($block['innerBlocks']) . '-columns';
    $content = substr_replace($block_content, $new_cls, strpos($block_content, $cls) + strlen($cls), 0);
    return $content;
  }
  return $block_content;
}
add_filter('render_block', 'add_class_to_wp_block_columns', 10, 2)

When the block passed in is a columns block, we find the position of the wp-block-columns class in the content. Then we insert after that class a new class has-x-columns, where x is the number of inner blocks. Note that we are not using the more straightforward str_replace function, since we only want to insert the class name once. This is because we only want to modify the class of the outermost div tag, not the inner ones, in the nested columns scenario.