A Crash Course in WordPress Block Filters

Blocks in WordPress are great. Drop some into the page, arrange them how you like, and you’ve got a pretty sweet landing page with little effort. But what if the default blocks in WordPress need a little tweaking? Like, what if we could remove the alignment options in the Cover block settings? Or how about control sizing for the Button block?

There are plenty of options when it comes to extending the functionality of core blocks in WordPress. We can add a custom CSS class to a block in the editor, add a custom style, or create a block variation. But even those might not be enough to get what you need, and you find yourself needing to filter the core block to add or remove features, or building an entirely new block from scratch.

I’ll show you how to extend core blocks with filters and also touch on when it’s best to build a custom block instead of extending a core one.

A quick note on these examples

Before we dive in, I’d like to note that code snippets in this article are taken out of context on purpose to focus on filters rather than build tools and file structure. If I included the full code for the filters, the article would be hard to follow. With that said, I understand that it’s not obvious for someone who is just starting out where to put the snippets or how to run build scripts and make the whole thing work.

To make things easier for you, I made a WordPress plugin with examples from this article available on my GitHub. Feel free to download it and explore the file structure, dependencies and build scripts. There is a README that will help you get started.

Block filters in an nutshell

The concept of filters is not new to WordPress. Most of us are familiar with the add_filter() function in PHP. It allows developers to modify various types of data using hooks.

A simple example of a PHP filter could look something like this:

function filter_post_title( $title ){
  return '<strong>' . $title . '</strong>';
};

add_filter( 'the_title',  'filter_post_title' );

In this snippet, we create a function that receives a string representing a post title, then wrap it in a <strong> tag and return a modified title. We then use add_filter() to tell WordPress to use that function on a post title.

JavaScript filters work in a similar way. There is a JavaScript function called addFilter() that lives in the wp.hooks package and works almost like its PHP sibling. In its simplest form, a JavaScript filter looks something like this:

function filterSomething(something) {
  // Code for modifying something goes here.
  return something;
}

wp.hooks.addFilter( 'hookName', 'namespace', filterSomething );

Looks pretty similar, right? One notable difference is addFilter() has a namespace as a second argument. As per the WordPress Handbook, “Namespace uniquely identifies a callback in the the form vendor/plugin/function.” However, examples in the handbook follow different patterns: plugin/what-filter-does or plugin/component-name/what-filter-does. I usually follow the latter because it keeps the handles unique throughout the project.

What makes JavaScript filters challenging to understand and use is the different nature of what they can filter. Some filter strings, some filter JavaScript objects, and others filter React components and require understanding the concept of Higher Order Components.

On top of that, you’ll most likely need to use JSX which means you can’t just drop the code into your theme or plugin and expect it to work. You need to transpile it to vanilla JavaScript that browsers understand. All that can be intimidating at the beginning, especially if you are coming from a PHP background and have limited knowledge of ES6, JSX, and React.

But fear not! We have two examples that cover the basics of block filters to help you grasp the idea and feel comfortable working with JavaScript filters in WordPress. As a reminder, if writing this code for the Block Editor is new to you, explore the plugin with examples from this article.

Without any further ado, let’s take a look at the first example.

Removing the Cover block’s alignment options

We’re going to filter the core Cover block and remove the Left, Center, Right, and Wide alignment options from its block settings. This may be useful on projects where the Cover block is only used as a page hero, or a banner of some sort and does not need to be left- or right-aligned.

We’ll use the blocks.registerBlockType filter. It receives the settings of the block and its name and must return a filtered settings object. Filtering settings allows us to update the supports object that contains the array of available alignments. Let’s do it step-by-step.

We’ll start by adding the filter that just logs the settings and the name of the block to the console, to see what we are working with:

const { addFilter } = wp.hooks;

function filterCoverBlockAlignments(settings, name) {
  console.log({ settings, name });
  return settings;
}

addFilter(
  'blocks.registerBlockType',
  'intro-to-filters/cover-block/alignment-settings',
  filterCoverBlockAlignments,
);

Let’s break it down. The first line is a basic destructuring of the wp.hooks object. It allows us to write addFilter() in the rest of the file, instead of wp.hooks.addFilter(). This may seem redundant in this case, but it is useful when using multiple filters in the same file (as we’ll get to in the next example).

Next, we defined the filterCoverBlockAlignments() function that does the filtering. For now, it only logs the settings object and the name of the block to the console and returns the settings as is.

All filter functions receive data, and must return filtered data. Otherwise, the editor will break.

And, lastly, we initiated the filter with addFilter() function. We provided it with the name of the hook we are going to use, the filter namespace, and a function that does the filtering.

If we’ve done everything right, we should see a lot of messages in the console. But note that not all of them refer to the Cover block.

This is correct because the filter is applied to all blocks rather than the specific one we want. To fix that, we need to make sure that we apply the filter only to the core/cover block:

function filterCoverBlockAlignments(settings, name) {
  if (name === 'core/cover') {
    console.log({ settings, name });
  }
  return settings;
}

With that in place, we should see something like this now in the console:

Don’t worry if you see more log statements than Cover blocks on the page. I have yet to figure out why that’s the case. If you happen to know why, please share in the comments!

And here comes the fun part: the actual filtering. If you have built blocks from scratch before, then you know that alignment options are defined with Supports API. Let me quickly remind you how it works — we can either set it to true to allow all alignments, like this:

supports: {
  align: true
}

…or provide an array of alignments to support. The snippet below does the same thing, as the one above:

supports: {
  align: [ 'left', 'right', 'center', 'wide', 'full' ]
}

Now let’s take a closer look at the settings object from one of the console messages we have and see what we are dealing with:

All we need to do is replace align: true with align: ['full'] inside the supports property. Here’s how we can do it:

function filterCoverBlockAlignments(settings, name) {
  if (name === 'core/cover') {
    return assign({}, settings, {
      supports: merge(settings.supports, {
        align: ['full'],
      }),
    });
  }
  return settings;
}

I’d like to pause here to draw your attention to the assign and merge lodash methods. We use those to create and return a brand new object and make sure that the original settings object remains intact. The filter will still work if we do something like this:

/* 👎 WRONG APPROACH! DO NOT COPY & PASTE! */
settings.supports.align = ['full'];
return settings;

…but that is an object mutation, which is considered a bad practice and should be avoided unless you know what you are doing. Zell Liew discusses why mutation can be scary over at A List Apart.

Going back to our example, there should now only be one alignment option in the block toolbar:

I removed the “center” alignment option because the alignment toolbar allows you to toggle the alignment “on” and “off.” This means that Cover blocks now have default and “Full width” states.

And here’s the full snippet:

const { addFilter } = wp.hooks;
const { assign, merge } = lodash;

function filterCoverBlockAlignments(settings, name) {
  if (name === 'core/cover') {
    return assign({}, settings, {
      supports: merge(settings.supports, {
        align: ['full'],
      }),
    });
}
  return settings;
}

addFilter(
  'blocks.registerBlockType',
  'intro-to-filters/cover-block/alignment-settings',
  filterCoverBlockAlignments,
);

This wasn’t hard at all, right? You are now equipped with a basic understanding of how filters work with blocks. Let’s level it up and take a look at a slightly more advanced example.

Adding a size control to the Button block

Now let’s add a size control to the core Button block. It will be a bit more advanced as we will need to make a few filters work together. The plan is to add a control that will allow the user to choose from three sizes for a button: Small, Regular, and Large.

The goal is to get that new “Size settings” section up and running.

It may seem complicated, but once we break it down, you’ll see that it’s actually pretty straightforward.

1. Add a size attribute to the Button block

First thing we need to do is add an additional attribute that stores the size of the button. We’ll use the already familiar blocks.registerBlockType filter from the previous example:

/**
 * Add Size attribute to Button block
 *
 * @param  {Object} settings Original block settings
 * @param  {string} name     Block name
 * @return {Object}          Filtered block settings
 */
function addAttributes(settings, name) {
  if (name === 'core/button') {
    return assign({}, settings, {
      attributes: merge(settings.attributes, {
        size: {
          type: 'string',
          default: '',
        },
      }),
    });
  }
  return settings;
}

addFilter(
  'blocks.registerBlockType',
  'intro-to-filters/button-block/add-attributes',
  addAttributes,
);

The difference between what we’re doing here versus what we did earlier is that we’re filtering attributes rather than the supports object. This snippet alone doesn’t do much and you won’t notice any difference in the editor, but having an attribute for the size is essential for the whole thing to work.

2. Add the size control to the Button block

We’re working with a new filter, editor.BlockEdit. It allows us to modify the Inspector Controls panel (i.e. the settings panel on the right of the Block editor).

/**
 * Add Size control to Button block
 */
const addInspectorControl = createHigherOrderComponent((BlockEdit) => {
  return (props) => {
    const {
      attributes: { size },
      setAttributes,
      name,
    } = props;
    if (name !== 'core/button') {
      return <BlockEdit {...props} />;
    }
    return (
      <Fragment>
        <BlockEdit {...props} />
        <InspectorControls>
          <PanelBody
            title={__('Size settings', 'intro-to-filters')}
            initialOpen={false}
          >
            <SelectControl
              label={__('Size', 'intro-to-filters')}
              value={size}
              options={[
                {
                  label: __('Regular', 'intro-to-filters'),
                  value: 'regular',
                },
                {
                  label: __('Small', 'intro-to-filters'),
                  value: 'small'
                },
                {
                  label: __('Large', 'intro-to-filters'),
                  value: 'large'
                },
              ]}
              onChange={(value) => {
                setAttributes({ size: value });
              }}
            />
          </PanelBody>
      </InspectorControls>
      </Fragment>
    );
  };
}, 'withInspectorControl');

addFilter(
  'editor.BlockEdit',
  'intro-to-filters/button-block/add-inspector-controls',
  addInspectorControl,
);

This may look like a lot, but we’ll break it down and see how straightforward it actually is.

The first thing you may have noticed is the createHigherOrderComponent construct. Unlike other filters in this example, editor.BlockEdit receives a component and must return a component. That’s why we need to use a Higher Order Component pattern derived from React.

In its purest form, the filter for adding controls looks something like this:

const addInspectorControl = createHigherOrderComponent((BlockEdit) => {
  return (props) => {
    // Logic happens here.
    return <BlockEdit {...props} />;
  };
}, 'withInspectorControl');

This will do nothing but allow you to inspect the <BlockEdit /> component and its props in the console. Hopefully the construct itself makes sense now, and we can keep breaking down the filter.

The next part is destructuring the props:

const {
  attributes: { size },
  setAttributes,
  name,
} = props;

This is done so we can use name, setAttributes, and size in the scope of the filter, where:

  • size is the attribute of the block that we’ve added in step 1.
  • setAttributes is a function that lets us update the block’s attribute values.
  • name is a name of the block. which is core/button in our case.

Next, we avoid inadvertantly adding controls to other blocks:

if (name !== 'core/button') {
  return <BlockEdit {...props} />;
}

And if we are dealing with a Button block, we wrap the settings panel in a <Fragment /> (a component that renders its children without a wrapping element) and add an additional control for picking the button size:

return (
  <Fragment>
    <BlockEdit {...props} />
    {/* Additional controls go here */}
  </Fragment>
);

Finally, additional controls are created like this:

<InspectorControls>
  <PanelBody title={__('Size settings', 'intro-to-filters')} initialOpen={false}>
    <SelectControl
      label={__('Size', 'intro-to-filters')}
      value={size}
      options={[
        { label: __('Regular', 'intro-to-filters'), value: 'regular' },
        { label: __('Small', 'intro-to-filters'), value: 'small' },
        { label: __('Large', 'intro-to-filters'), value: 'large' },
      ]}
      onChange={(value) => {
        setAttributes({ size: value });
      }}
    />
  </PanelBody>
</InspectorControls>

Again, if you have built blocks before, you may already be familiar with this part. If not, I encourage you to study the library of components that WordPress comes with.

At this point we should see an additional section in the inspector controls for each Button block:

We are also able to save the size, but that won’t reflect in the editor or on the front end. Let’s fix that.

3. Add a size class to the block in the editor

As the title suggests, the plan for this step is to add a CSS class to the Button block so that the selected size is reflected in the editor itself.

We’ll use the editor.BlockListBlock filter. It is similar to editor.BlockEdit in the sense that it receives the component and must return the component; but instead of filtering the block inspector panel, if filters the block component that is displayed in the editor.

import classnames from 'classnames';
const { addFilter } = wp.hooks;
const { createHigherOrderComponent } = wp.compose;

/**
 * Add size class to the block in the editor
 */
const addSizeClass = createHigherOrderComponent((BlockListBlock) => {
  return (props) => {
    const {
      attributes: { size },
      className,
      name,
    } = props;

    if (name !== 'core/button') {
      return <BlockListBlock {...props} />;
    }

    return (
      <BlockListBlock
        {...props}
        className={classnames(className, size ? `has-size-${size}` : '')}
      />
    );
  };
}, 'withClientIdClassName');

addFilter(
   'editor.BlockListBlock',
   'intro-to-filters/button-block/add-editor-class',
   addSizeClass
);

You may have noticed a similar structure already:

  1. We extract the size, className, and name variables from props.
  2. Next, we check if we are working with core/button block, and return an unmodified <BlockListBlock> if we aren’t.
  3. Then we add a class to a block based on selected button size.

I’d like to pause on this line as it may look confusing from the first glance:

className={classnames(className, size ? `has-size-${size}` : '')}

I’m using the classnames utility here, and it’s not a requirement — I just find using it a bit cleaner than doing manual concatenations. It prevents me from worrying about forgetting to add a space in front of a class, or dealing with double spaces.

4. Add the size class to the block on the front end

All we have done up to this point is related to the Block Editor view, which is sort of like a preview of what we might expect on the front end. If we change the button size, save the post and check the button markup on the front end, notice that button class is not being applied to the block.

To fix this, we need to make sure we are actually saving the changes and adding the class to the block on the front end. We do it with blocks.getSaveContent.extraProps filter, which hooks into the block’s save() function and allows us to modify the saved properties. This filter receives block props, the type of the block, and block attributes, and must return modified block props.

import classnames from 'classnames';
const { assign } = lodash;
const { addFilter } = wp.hooks;

/**
 * Add size class to the block on the front end
 *
 * @param  {Object} props      Additional props applied to save element.
 * @param  {Object} block      Block type.
 * @param  {Object} attributes Current block attributes.
 * @return {Object}            Filtered props applied to save element.
 */
function addSizeClassFrontEnd(props, block, attributes) {
  if (block.name !== 'core/button') {
    return props;
  }

  const { className } = props;
  const { size } = attributes;

  return assign({}, props, {
    className: classnames(className, size ? `has-size-${size}` : ''),
  });
}

addFilter(
  'blocks.getSaveContent.extraProps',
  'intro-to-filters/button-block/add-front-end-class',
  addSizeClassFrontEnd,
);

In the snippet above we do three things:

  1. Check if we are working with a core/button block and do a quick return if we are not.
  2. Extract the className and size variables from props and attributes objects respectively.
  3. Create a new props object with an updated className property that includes a size class if necessary.

Here’s what we should expect to see in the markup, complete with our size class:

<div class="wp-block-button has-size-large">
  <a class="wp-block-button__link" href="#">Click Me</a>
</div>

5. Add CSS for the custom button sizes

One more little thing before we’re done! The idea is to make sure that large and small buttons have corresponding CSS styles.

Here are the styles I came up with:

.wp-block-button.has-size-large .wp-block-button__link {
  padding: 1.5rem 3rem;
}
.wp-block-button.has-size-small .wp-block-button__link {
  padding: 0.25rem 1rem;
}

If you are building a custom theme, you can include these front-end styles in the theme’s stylesheet. I created a plugin for the default Twenty Twenty One theme, so, in my case, I had to create a separate stylesheet and include it using wp_enqueue_style(). You could just as easily work directly in functions.php if that’s where you manage functions.

function frontend_assets() {
  wp_enqueue_style(
    'intro-to-block-filters-frontend-style',
    plugin_dir_url( __FILE__ ) . 'assets/frontend.css',
    [],
    '0.1.0'
  );
}
add_action( 'wp_enqueue_scripts', 'frontend_assets' );

Similar to the front end, we need to make sure that buttons are properly styled in the editor. We can include the same styles using the enqueue_block_editor_assets action:

function editor_assets() {
  wp_enqueue_style(
    'intro-to-block-filters-editor-style',
    plugin_dir_url( __FILE__ ) . 'assets/editor.css',
    [],
    '0.1.0'
  );
}
add_action( 'enqueue_block_editor_assets', 'editor_assets' );

We should now should have styles for large and small buttons on the front end and in the editor!

As I mentioned earlier, these examples are available in as a WordPress plugin I created just for this article. So, if you want to see how all these pieces work together, download it over at GitHub and hack away. And if something isn’t clear, feel free to ask in the comments.

Use filters or create a new block?

This is a tricky question to answer without knowing the context. But there’s one tip I can offer.

Have you ever seen an error like this?

It usually occurs when the markup of the block on the page is different from the markup that is generated by the block’s save() function. What I’m getting at is it’s very easy to trigger this error when messing around with the markup of a block with filters.

So, if you need to significantly change the markup of a block beyond adding a class, I would consider writing a custom block instead of filtering an existing one. That is, unless you are fine with keeping the markup consistent for the editor and only changing the front-end markup. In that case, you can use PHP filter.

Speaking of which…

Bonus tip: render_block()

This article would not be complete without mentioning the render_block hook. It filters block markup before it’s rendered. It comes in handy when you need to update the markup of the block beyond adding a new class.

The big upside of this approach is that it won’t cause any validation errors in the editor. That said, the downside is that it only works on the front end. If I were to rewrite the button size example using this approach, I would first need to remove the code we wrote in the fourth step, and add this:

/**
 * Add button size class.
 *
 * @param  string $block_content Block content to be rendered.
 * @param  array  $block         Block attributes.
 * @return string
 */
function add_button_size_class( $block_content = '', $block = [] ) {
  if ( isset( $block['blockName'] ) && 'core/button' === $block['blockName'] ) {
    $defaults = ['size' => 'regular'];
    $args = wp_parse_args( $block['attrs'], $defaults );

    $html = str_replace(
      '<div class="wp-block-button',
      '<div class="wp-block-button has-size-' . esc_attr( $args['size']) . ' ',
      $block_content
    );

    return $html;
}
  return $block_content;
}

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

This isn’t the cleanest approach because we are injecting a CSS class using str_replace() — but that’s sometimes the only option. A classic example might be working with a third-party block where we need to add a <div> with a class around it for styling.

Wrapping up

WordPress block filters are powerful. I like how it allows you to disable a lot of unused block options, like we did with the Cover block in the first example. This can reduce the amount of CSS you need to write which, in turn, means a leaner stylesheet and less maintenance — and less cognitive overhead for anyone using the block settings.

But as I mentioned before, using block filters for heavy modifications can become tricky because you need to keep block validation in mind.

That said, I usually reach for block filters if I need to:

  • disable certain block features,
  • add an option to a block and can’t/don’t want to do it with custom style (and that option must not modify the markup of the block beyond adding/removing a custom class), or
  • modify the markup only on the front end (using a PHP filter).

I also usually end up writing custom blocks when core blocks require heavy markup adjustments both on the front end and in the editor.

If you have worked with block filters and have other thoughts, questions, or comments, let me know!

Resources


The post A Crash Course in WordPress Block Filters appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

How to Use Block Variations in WordPress

WordPress 5.4 was released not so long ago and, along with other improvements and bug fixes, it introduced a feature called Block Variations. I had a chance to use it on one of my recent projects and am so pleasantly surprised with how smart this feature is. I actually think it hasn’t received the attention it deserves, which is why I decided to write this article.

What is a Block Variation?

Block Variations allow developers to define instances of existing blocks. An example that you’ll see below is a quote block. Perhaps your site has three variations of how to display a quote on your site. A Block Variation can be created for each one so that they are all styled differently. This sounds awfully familiar with how Block Styles, but the concept of variations goes a bit further than that, as we’ll see.

How are Block Variations different from Block Styles?

Fair question. Block variations appear in the inserter as separate blocks with unique names and (optionally) icons and can have pre-filled custom attributes, and inner blocks.

Block Styles are designed to alter the look of the block. In fact, a Block Style is a fancy way of adding a custom class to a block using the Block options in the post editor.

The difference is clear when you consider how each one is used in the post editor. Let’s say we register a new Block Style called “Fancy Quote.” We do that by extending the core “Quote” block like this example from the Block Editor Handbook:

wp.blocks.registerBlockStyle(
  'core/quote',
  {
    name: 'fancy-quote',
    label: 'Fancy Quote'
  },
);

This adds a .is-style-fancy-quote class to the Quote block settings in the post editor.

Screenshot of the Block options in the WordPress post editor highlighting the options for a quote block. A "Fancy Quote" option is listed under Styles and the custom class name is in an Additional CSS Classes field.
We now have a Fancy Quote option in the Block options under “Styles” and the class for it filled in for us.

Even though it sort of sounds like it would do the same thing (which it technically can), a Block Variation can be used to pre-fill custom attributes (including custom classes) and inner blocks. They’re actually registered as separate blocks.

Let’s take a closer look at the API and what block variations can do.

Creating a Block Variation

The API for registering Block Variations is very similar that of the Block Style we just looked at:

wp.blocks.registerBlockVariation(
  'core/quote',
  {
    name: 'fancy-quote',
    title: 'Fancy Quote',
  },
);

The registerBlockVariation function accepts the name of the block (in our case it is core/quote) and an object (or an array of objects) describing the variation(s).

The code above doesn’t do much by default, but it does add “Fancy Quote” to the list of available blocks.

Showing the Fancy Quote variation in the WordPress Block Inserter.
We now have two different “quote” blocks available to drop into the post.

To take full advantage of the variation. we need to provide more details in the object describing it. The list is covered in the Make WordPress post, but I’ll share it here and provide additional comments.

  • name – The unique and machine-readable name of the variation. Following the examples on Github and Make post it’s safe to assume that the best practice is to use kebab-case for naming variations.
  • title – A human-readable variation title. This is what appears under the icon in the Inserter.
  • description – A detailed variation description. Appears in the Inserter as well. If empty, the default block description will be used. (Optional)
  • icon – An icon for the variation. Can be a Dashicons slug, an SVG or an object. Follows the same declaration pattern as in registerBlockType. (Optional)
  • isDefault – Indicates whether the current variation is the default one. Defaults to false. In case of our example, if we set it to true, the Fancy Quote block will be the only Quote block available in the inserter. (Optional)
  • attributes – Values that override block attributes. These are block-specific. You can set the level for the Heading block or height for Spacer, for example.
  • innerBlocks – Initial configuration of nested blocks. Only applies to blocks that allow inner blocks in the first place, like Columns, Cover, or Group. We’ll cover this in one of the examples. (Optional)
  • example – Example provides structured data for the block preview. You can set it to undefined to disable the preview shown for the block type. This is the same as the example field in registerBlockType. (Optional) There’s more information available on this parameter.
  • scope – The list of scopes where the variation is applicable. When not provided, it assumes all available scopes. Available options are block and inserter. We’ll cover this in detail in one of the examples.

Many of you may wonder why we need this extra layer of abstraction. Let me try to answer that with a few use cases (one form my recent project).

Use case: Buttons with different widths

Let’s say you have a design system with two types of buttons: Fill and Outline.

Two buttons, one with a green fill and one with a green border. Both say Learn More.
Fill and Outline button styles in the design system

Lucky you, because these are the default styles for buttons in WordPress. No need to register any new styles or hack the editor. All you have to do is write some CSS for each style and call it a day. Life is good and everybody’s happy.

But then you look in the design spec again and notice that there is a little twist. The buttons come in three widths: Regular, Wide, and Full.

The same green buttons but with additional variations at two different widths for a totally of six buttons.
Fill and Outline button styles with different width variations

Dammit! You are a little upset because you now have two options:

  1. Write two extra classes for the new button sizes (say, .is-wide and .is-full), then teach the client to use the Advanced panel in the editor to add those classes and write a manual where you explain what each class does. Or…
  2. Register four(!) new styles that go in the Block options: Fill Wide, Fill Full, Outline Wide, and Outline Full.

Neither of those options are exactly elegant. (BTW, what is Fill Full exactly? Quite an unfortunate mouthful!)

There are two more options that I didn’t include in the list:

  • Filter the button block and add a custom width control to it
  • Build a custom block from scratch.

These obviously feel like heavy lifts for such a simple task.

Enter Block Variations! By adding just two variations, Full and Wide, we can keep things clean and simple:

wp.blocks.registerBlockVariation(
  'core/buttons',
  [
    {
      name: 'wide',
      title: 'Wide Buttons',
      attributes: {
        className: 'is-wide'
      },
  },
  {
      name: 'full',
      title: 'Full Buttons',
      attributes: {
        className: 'is-full'
      },
    }
  ]
);

This is the same as adding a custom class to the Buttons block, but in a neat and elegant way that can be dropped directly into a post from the Block Inserter:

Showing the Wide and Full button variations in the WordPress Block Inserter.
Button variations in the inserter

Life is good and everybody is happy again! So what did we learn from this example?

  • It shows that Block Variations are not designed to replace Block Styles. In fact, they can work pretty well together even if the variation just adds a class to a block.
  • It demos how to register multiple variations in a single declaration.

Use case: Repeating column layouts

Let’s say you are a designer and have a portfolio website with case studies. Each case study has an intro section with the name of the project, client information, and a description of your role on the project. It might look something like this:

Showing three columns, one that says Website Design, one that says Clients, and one says Role. Each one represents a column we want on the page.
The type of work (left), who it was for (center) and your role on it (right)

The problem is that it’s a bit tedious to build this part of the layout every time you create a new portfolio case study — especially because the Client and My Role headings never change. You are only editing the main title and two paragraphs.

With Block Variations, you can create a variation of a core Columns block called Project Intro that will have the columns, and inner blocks already defined. This example is a bit more involved, so we’ll build it out step-by-step.

Let’s start with registering the variation:

wp.blocks.registerBlockVariation(
  'core/columns', {
    name: 'project-intro',
    title: 'Project Intro',
    scope: ['inserter'],
    innerBlocks: [
      ['core/column'],
      ['core/column'],
      ['core/column'],
    ],
  }
);

We are taking this example a bit further than the first one, so why not add a custom portfolio icon from the Dashicons library that’s baked right into WordPress? We do that with the icon property.

wp.blocks.registerBlockVariation(
  'core/columns', {
    name: 'project-intro',
    title: 'Project Intro',
    icon: 'portfolio',
    scope: ['inserter'],
    innerBlocks: [
      ['core/column'],
      ['core/column'],
      ['core/column'],
    ],
  }
);

This will make the block available in the block menu with our icon:

Variation with a custom icon in the Block Inserter.

The next important thing happens on where we add inner blocks:

wp.blocks.registerBlockVariation(
  'core/columns', {
    name: 'project-intro',
    title: 'Project Intro',
    icon: 'portfolio',
    scope: ['inserter'],
    innerBlocks: [
      ['core/column'],
      ['core/column'],
      ['core/column'],
    ],
  }
);

But this only gives us three empty columns. Let’s add starter content and inner blocks to each of them. We can use the same pattern we use to declare a block template in the InnerBlocks component. We can add an object with block attributes as a second element in the array describing the block, and an array of inner blocks as the third element.

The first column will look like this:

['core/column', {}, [
  ['core/heading', { level: 2, placeholder: 'Project Title'} ],
]]

…and the complete block variation is like this:

wp.blocks.registerBlockVariation (
  'core/columns', {
    name: 'project-intro',
    title: 'Project Intro',
    icon: 'portfolio',
    scope: ['inserter'],
    innerBlocks: [
      ['core/column', {}, [
        ['core/heading', { level: 2, placeholder: 'Project Title' }],
      ]],
      ['core/column', {}, [
        ['core/heading', { level: 3, content: 'Client' }],
        ['core/paragraph', { placeholder: 'Enter client info' }],
      ]],
      ['core/column', {}, [
        ['core/heading', { level: 3, content: 'My Role' }],
        ['core/paragraph', { placeholder: 'Describe your role' }],
      ]],
    ],
  }
);

Cool, now we can insert the whole section with just one click. Okay, it’s a few clicks, but still faster than without using the variations.

So what did we learn from this example?

  • And demos how to use the inner blocks within the variation
  • It shows how to define a custom icon for a variation

Use case: Four-column layout

You already know that columns are a default block type, and that there are a handful of options for different types of columns. A four-column layout isn’t one of them, so we can build that. But this introduces a new concept as well: scoping in context of block variations.

Some core blocks, like Columns, already offer variations out of the box. You can choose one of them after you insert the block on the page:

Showing the columns block inserted with 5 different layout options to display up to 3 columns at varying widths.
Block-scoped variations

Let’s say you use a four-column layout on your website as often as you use two-column one. That’s unfortunate, because there is no shortcut button to create four-column layout. Creating one is a bit annoying because it takes extra clicks to get to the Columns control after the block is inserted:

Showing the slider control to change the number of columns in the Block settings.

So, what can you do to improve this workflow? Right, you can add a Block Variation that will create a four-column layout. The only difference this time, compared to previous examples, is that it makes much more sense to include this variation inside the block placeholder, next to all other column layouts.

That is exactly what the scope option is for. If you set it to [block], the variation will not appear in the Block Inserter but in the variations once the block has been inserted.

wp.blocks.registerBlockVariation(
  'core/columns', {
    name: 'four-columns',
    title: 'Four columns; equal split',
    icon: <svg ... />,
    scope: ['block'], // Highlight
    innerBlocks: [
      ['core/column'],
      ['core/column'],
      ['core/column'],
      ['core/column'],
    ],
  }
);
Four-column layout variation scoped to the block.
Hey, now we have a four-column option!

Isn’t that sweet?!

I’ve omitted the full SVG code for the icon, but it’s available if you need it.

To sum up scope: If it isn’t declared, the variation will appear in the Block Inserter and inside the block placeholder — specifically for blocks that support block-scoped variations. 

If we were to remove the scope parameter from the example above, here’s how the variation would appear in the inserter:

Four-column block variation in the block inserter.
Keep in mind that the icon sizes for variations within the block and and the block icons size are different. The custom icon for columns was intended for the block scope, that’s why it looks a bit out-of-place in this example.

So what did we learn from this example?

  • It explains the difference between the block and inserter scope for the variation.
  • We learned how to use SVG for variation icon.

That’s it!

As you can see, Block Variations are pretty powerful for building a lot of things, from different variations of buttons to complete page layouts.

I’d like to wrap this up with a quick recap of different APIs for block customizations and when to use them:

  • Use Block Styles if you need to alter the appearance of the block and adding a CSS class is enough for that.
  • Use Block Variations if you need to specify the default attributes for the block and/or add inner blocks to it.
  • If that’s not enough and you need to change the markup of the block, you are probably looking into filtering the block or creating a new one from scratch.

If you’ve had a chance to play with Block Variation, let me know what you think of them in the comments!

Resources

The post How to Use Block Variations in WordPress appeared first on CSS-Tricks.

How to Get the Current Page URL in Gatsby

This seemingly simple task had me scratching my head for a few hours while I was working on my website. As it turns out, getting the current page URL in Gatsby is not as straightforward as you may think, but also not so complicated to understand.

Let’s look at a few methods of making it happen. But first, you might be wondering why on earth we’d even want to do something like this.

Why you might need the current URL

So before we get into the how, let’s first answer the bigger question: Why would you want to get the URL of the current page? I can offer a few use cases.

Meta tags

The first obvious thing that you’d want the current URL for is meta tags in the document head:

<link rel="canonical" href={url} />
<meta property="og:url" content={url} />

Social Sharing

I’ve seen it on multiple websites where a link to the current page is displayed next to sharing buttons. Something like this (found on Creative Market)

Styling

This one is less obvious but I’ve used it a few times with styled-components. You can render different styles based on certain conditions. One of those conditions can be a page path (i.e. part of the URL after the name of the site). Here’s a quick example:

import React from 'react';
import styled from 'styled-components';

const Layout = ({ path, children }) => (
  <StyledLayout path={path}>
    {children}
  </StyledLayout>
);
    
const StyledLayout = styled.main`
  background-color: ${({ path }) => (path === '/' ? '#fff' : '#000')};
`;

export default Layout;

Here, I’ve created a styled Layout component that, based on the path, has a different background color.

This list of examples only illustrates the idea and is by no means comprehensive. I’m sure there are more cases where you might want to get the current page URL. So how do we get it?

Understand build time vs. runtime

Not so fast! Before we get to the actual methods and code snippets, I’d like to make one last stop and briefly explain a few core concepts of Gatsby.

The first thing that we need to understand is that Gatsby, among many other things, is a static site generator. That means it creates static files (that are usually HTML and JavaScript). There is no server and no database on the production website. All pieces of information (including the current page URL) must be pulled from other sources or generated during build time or runtime before inserting it into the markup.

That leads us to the second important concept we need to understand: Build time vs. runtime. I encourage you to read the official Gatsby documentation about it, but here’s my interpretation.

Runtime is when one of the static pages is opened in the browser. In that case, the page has access to all the wonderful browser APIs, including the Window API that, among many other things, contains the current page URL.

One thing that is easy to confuse, especially when starting out with Gatsby, is that running gatsby develop in the terminal in development mode spins up the browser for you. That means all references to the window object work and don’t trigger any errors.

Build time happens when you are done developing and tell Gatsby to generate final optimized assets using the gatsby build command. During build time, the browser doesn’t exist. This means you can’t use the window object.

Here comes the a-ha! moment. If builds are isolated from the browser, and there is no server or database where we can get the URL, how is Gatsby supposed to know what domain name is being used? That’s the thing — it can’t! You can get the slug or path of the page, but you simply can’t tell what the base URL is. You have to specify it.

This is a very basic concept, but if you are coming in fresh with years of WordPress experience, it can take some time for this info to sink in. You know that Gatsby is serverless and all but moments like this make you realize: There is no server.

Now that we have that sorted out, let’s jump to the actual methods for getting the URL of the current page.

Method 1: Use the href property of the window.location object

This first method is not specific to Gatsby and can be used in pretty much any JavaScript application in the browser. See, browser is the key word here.

Let’s say you are building one of those sharing components with an input field that must contain the URL of the current page. Here’s how you might do that:

import React from 'react';

const Foo = () => {
  const url = typeof window !== 'undefined' ? window.location.href : '';

  return (
    <input type="text" readOnly="readonly" value={url} />
  );
};

export default Foo;

If the window object exists, we get the href property of the location object that is a child of the window. If not, we give the url variable an empty string value.

If we do it without the check and write it like this:

const url = window.location.href;

...the build will fail with an error that looks something like this:

failed Building static HTML for pages - 2.431s
ERROR #95312 
"window" is not available during server-side rendering.

As I mentioned earlier, this happens because the browser doesn’t exist during the build time. That’s a huge disadvantage of this method. You can’t use it if you need the URL to be present on the static version of the page.

But there is a big advantage as well! You can access the window object from a component that is nested deep inside other components. In other words, you don’t have to drill the URL prop from parent components.

Method 2: Get the href property of location data from props

Every page and template component in Gatsby has a location prop that contains information about the current page. However, unlike window.location, this prop is present on all pages.

Quoting Gatsby docs:

The great thing is you can expect the location prop to be available to you on every page.

But there may be a catch here. If you are new to Gatsby, you’ll log that prop to the console, and notice that it looks pretty much identical to the window.location (but it’s not the same thing) and also contains the href attribute. How is this possible? Well, it is not. The href prop is only there during runtime.

The worst thing about this is that using location.href directly without first checking if it exists won’t trigger an error during build time.

All this means that we can rely on the location prop to be on every page, but can’t expect it to have the href property during build time. Be aware of that, and don’t use this method for critical cases where you need the URL to be in the markup on the static version of the page.

So let’s rewrite the previous example using this method:

import React from 'react';

const Page = ({ location }) => {
  const url = location.href ? location.href : '';

  return (
    <input type="text" readOnly="readonly" value={url} />
  );
};

export default Page;

This has to be a top-level page or template component. You can’t just import it anywhere and expect it work. location prop will be undefined.

As you can see, this method is pretty similar to the previous one. Use it for cases where the URL is needed only during runtime.

But what if you need to have a full URL in the markup of a static page? Let’s move on to the third method.

Method 3: Generate the current page URL with the pathname property from location data

As we discussed at the start of this post, if you need to include the full URL to the static pages, you have to specify the base URL for the website somewhere and somehow get it during build time. I’ll show you how to do that.

As an example, I’ll create a <link rel="canonical" href={url} /> tag in the header. It is important to have the full page URL in it before the page hits the browser. Otherwise, search engines and site scrapers will see the empty href attribute, which is unacceptable.

Here’s the plan:

  1. Add the siteURL property to siteMetadata in gatsby-config.js.
  2. Create a static query hook to retrieve siteMetadata in any component.
  3. Use that hook to get siteURL.
  4. Combine it with the path of the page and add it to the markup.

Let’s break each step down.

Add the siteURL property to siteMetadata in gatsby-config.js

Gatsby has a configuration file called gatsby-config.js that can be used to store global information about the site inside siteMetadata object. That works for us, so we’ll add siteURL to that object:

module.exports = {
  siteMetadata: {
    title: 'Dmitry Mayorov',
    description: 'Dmitry is a front-end developer who builds cool sites.',
    author: '@dmtrmrv',
    siteURL: 'https://dmtrmrv.com',
  }
};

Create a static query hook to retrieve siteMetadata in any component

Next, we need a way to use siteMetadata in our components. Luckily, Gatsby has a StaticQuery API that allows us to do just that. You can use the useStaticQuery hook directly inside your components, but I prefer to create a separate file for each static query I use on the website. This makes the code easier to read.

To do that, create a file called use-site-metadata.js inside a hooks folder inside the src folder of your site and copy and paste the following code to it.

import { useStaticQuery, graphql } from 'gatsby';

const useSiteMetadata = () => {
  const { site } = useStaticQuery(
  graphql`
    query {
    site {
      siteMetadata {
      title
      description
      author
      siteURL
      }
    }
    }
  `,
  );
  return site.siteMetadata;
};

export default useSiteMetadata;

Make sure to check that all properties — like title, description, author, and any other properties you have in the siteMetadata object — appear in the GraphQL query.

Use that hook to get siteURL

Here’s the fun part: We get the site URL and use it inside the component.

import React from 'react';
import Helmet from 'react-helmet';
import useSiteMetadata from '../hooks/use-site-metadata';

const Page = ({ location }) => {
  const { siteURL } = useSiteMetadata();
  return (
    <Helmet>
      <link rel="canonical" href={`${siteURL}${location.pathname}`} />
    </Helmet>
  );
};

export default Page;

Let’s break it down.

On Line 3, we import the useSiteMetadata hook we created into the component.

import useSiteMetadata from '../hooks/use-site-metadata';

Then, on Line 6, we destructure the data that comes from it, creating the siteURL variable. Now we have the site URL that is available for us during build and runtime. Sweet!

const { siteURL } = useSiteMetadata();

Combine the site URL with the path of the page and add it to the markup

Now, remember the location prop from the second method? The great thing about it is that it contains the pathname property during both build and runtime. See where it’s going? All we have to do is combine the two:

`${siteURL}${location.pathname}`

This is probably the most robust solution that will work in the browsers and during production builds. I personally use this method the most.

I’m using React Helmet in this example. If you haven’t heard of it, it’s a tool for rendering the head section in React applications. Darrell Hoffman wrote up a nice explanation of it here on CSS-Tricks.

Method 4: Generate the current page URL on the server side

What?! Did you just say server? Isn’t Gatsby a static site generator? Yes, I did say server. But it’s not a server in the traditional sense.

As we already know, Gatsby generates (i.e. server renders) static pages during build time. That’s where the name comes from. What’s great about that is that we can hook into that process using multiple APIs that Gatsby already provides.

The API that interests us the most is called onRenderBody. Most of the time, it is used to inject custom scripts and styles to the page. But what’s exciting about this (and other server-side APIs) is that it has a pathname parameter. This means we can generate the current page URL “on the server."

I wouldn’t personally use this method to add meta tags to the head section because the third method we looked at is more suitable for that. But for the sake of example, let me show you how you could add the canonical link to the site using onRenderBody.

To use any server-side API, you need to write the code in a file called gatsby-ssr.js that is located in the root folder of your site. To add the link to the head section, you would write something like this:

const React = require('react');
const config = require('./gatsby-config');

exports.onRenderBody = ({ pathname, setHeadComponents }) => {
  setHeadComponents([
    <link rel="canonical" href={`${config.siteMetadata.siteURL}${pathname}`} />,
  ]);
};

Let’s break this code bit by bit.

We require React on Line 1. It is necessary to make the JSX syntax work. Then, on Line 2, we pull data from the gatsby-config.js file into a config variable.

Next, we call the setHeadComponents method inside onRenderBody and pass it an array of components to add to the site header. In our case, it’s just one link tag. And for the href attribute of the link itself, we combine the siteURL and the pathname:

`${config.siteMetadata.siteURL}${pathname}`

Like I said earlier, this is probably not the go-to method for adding tags to the head section, but it is good to know that Gatsby has server-side APIs that make it possible to generate a URL for any given page during the server rendering stage.

If you want to learn more about server-side rendering with Gatsby, I encourage you to read their official documentation.

That’s it!

As you can see, getting the URL of the current page in Gatsby is not very complicated, especially once you understand the core concepts and know the tools that are available to use. If you know other methods, please let me know in the comments!

Resources

The post How to Get the Current Page URL in Gatsby appeared first on CSS-Tricks.