WordPress, do I need to learn it?

An image showing Front End developer learning WordPress

Since the introduction of Gutenberg in WordPress, the line between front-end development and back-end/WordPress development has become increasingly blurry.

I’m an FE-dev!

Since the introduction of Gutenberg in WordPress, the line between front-end development and back-end/WordPress development has become increasingly blurry. With the advent of Full Site Editing, the concept of “pure front-end development” is almost non-existent:

  • Standard settings such as colors, fonts, and spacing system should be set in theme.json, and the styles should use resulting --wp--preset--* variables as much as possible.
  • Many “local” styles should be available for the user as block customization options, not hardcoded styles/classes.
  • Ideally, we should be able to reuse developed blocks between projects when it makes sense, which means they should be as self-contained as possible.

As a result, the “let’s create a common style base and reuse it in components throughout the site, write hardcoded HTML, and let WordPress developers worry about connecting it with real data” approach makes developing new WordPress themes an exercise in frustration. The changes required to transform hardcoded solutions to theme modules can make it more feasible to scrap 80% of the “ready” code and start from (almost) scratch. Not to mention a high chance of creating a codebase resembling a Frankenstein monster ☣️ more than a long-term, maintainable solution.

It’s a lose-lose-lose situation for developers (because of mounting frustration 🤬), clients (because quality can suffer 😨), and company (because developers’ and QA team time is money 💸 and the unhappy client will tell many more about his frustrations and certainly will not come back 🙅‍♂️).

…ugh, fine…

I’m glad you agree 😉. I have good news, though! The creation of basic Gutenberg blocks can be significantly simplified with the ACF PRO plugin, and we have a company-wide license to use it in customer projects 🎉.

Since I’ve heard whispers about a new company-wide WP-starter theme, I will try to keep this focused on ACF Blocks only, but keep in mind that it is an opinionated guide, and some things may change after the starter is released. So, like and subscribe 😆.

The meat

We will create a (primitive) “Skewed section” ACF Block to learn the ropes. The idea is simple – it is a full-width block that has a background color, and its top and bottom edges are angled instead of horizontal. The end result will look like this in Gutenberg Editor:

An image showing WordPress admin editor, a gutenberg block.

A ready-to-use “Skewed section” block.

WordPress Theme Structure

To create an ACF Block, you need block files and a Custom Fields definition associated with that block. I like creating files first and fields later, but it doesn’t matter since you will probably edit both multiple times. There also needs to be block-loading functionality added to the theme – I’m going to assume that works “out of the box” in your project (hint: look for inc/Blocks.php or similar in the theme dir).

An image showing WordPress FE feature

Theme files structure

WordPress Theme Files

For simplicity’s sake (and because I firmly believe it isn’t needed for blocks, especially with all the CSS vars WordPress creates for us 😉), the block we will create uses plain CSS, not SCSS. Since we don’t need any JS, we need only three block files (usually placed in theme-name/blocks/block-name dir; in this case, theme-name=fse-starter and block-name=skewed-section):

  • block.json – contains all block metadata, virtually identical to any WP block definition, but with an extra acf section.
  • render.php – where stuff happens. In the simplest case, you pull several field values and echo them in the correct places. Other than that, it is like a simple, standalone component HTML file.
  • style.css – because if you don’t need any extra styling (or JS), you probably don’t need to create a new block at all 😆. Just create it in WYSIWYG and save it as a pattern.

In more advanced use cases, you may need/want additional files like script.js (if the block requires some JS-powered features) or editor.css (to overwrite how the block looks in the editor for a better experience).

Our basic block definition can look like this:

{
  "$schema": "<https://schemas.wp.org/trunk/block.json>",
  "apiVersion": 3,
  "name": "fse-starter/skewed-section",
  "title": "Skewed section",
  "category": "design",
  "icon": "<svg width='100%' height='100%' viewBox='0 0 512 512' version='1.1' xmlns='<http://www.w3.org/2000/svg>'><path d='M60,460l0,-400l400,100l0,200l-400,100Z'/></svg>",
  "description": "Section with top/bottom skew specified in degrees.",
  "keywords": [ "section", "skewed" ],
  "version": "1.0.0",
  "textdomain": "fse-starter",
  "style": [ "block-skewed-section" ],
  "acf": {
    "mode": "preview",
    "renderTemplate": "render.php",
    "blockVersion": 2
  },
  "supports": {},
  "attributes": {}
}

Most of the fields are self-explanatory, but several of them are worth a note:

  • name – by convention, this should be a block name slug, prefixed with the theme (or plugin) name slug
  • category – WordPress allows specifying a single category (group in the inserter interface) per the block. Usually, either one of WP’s predefined categories is used (text, media, design, widgets, theme, or embed), or a custom category is separately registered for theme blocks.
  • icon – should represent the block function, can be a string of SVG code or a slug of a dashicon (without dashicon-* prefix).
  • style – in the most common case, this will be an array containing a single item, block-*block-name* . The style file with such a handle is registered during the block registration process.
  • acf->mode – this can be one of preview, edit, and auto values. Preview is most useful when the block will contain nested content. The user sees the rendered block, and ACF fields are displayed in the sidebar. Edit mode means the user only sees ACF fields in the main editor window; the rendered view must be manually shown. Auto mode means the rendered view is visible by default but switches to edit when the block is selected.

Initially, the other block files can be left blank.

ACF Fields

The block fields should be intuitive and not overwhelm the user, especially if the block is intended for the end user. Here’s a shoutout for the @QATeam: if you don’t find options/names/default values intuitive, that should be an issue to point out during the QA process. The editing experience should be checked!

In our example, the block has three fields – two that are obvious and one that can be a bit surprising:

  • top/bottom skew value, in degrees; and
  • background color.

…wait, background color? But shouldn’t that be a “native” WordPress Block style field? Typically, and in most of your work – yes. I will return to why it will not work in this case when explaining the final code, so bear with me for now.

The critical step of creating block custom fields is assigning them to the correct block. The block must be visible in the WordPress to appear in the options list, so it is better to create block files as a first step. It is also a good practice to prefix the field group name with “Block” to allow fast identification of the group purpose.

An image showing WordPress ACF admin section

Fields in the “Skewd section” block.

Other than that – take your time to familiarize yourself with the available field types to be able to select the best one for the job. Some extra plugins can be installed to add new types, e.g. FocusPoint, Image Focus, or Table Field. You can also easily set some basic validation and enhance UX using the field Presentation tab.

An image showing WordPress  ACF settings section.

Available options for field editing.

MVE

block.json

To ensure our “Skewed section” block plays nicely with the editor and other elements on the page, we will ensure it supports some “native” WordPress block settings and that they have sensible defaults set: block align (by default, we want the section to be full width), ability to set the custom anchor (ID), and some sensible vertical margins.

As can be seen below, the list of options the block should handle is in the supports object, while default values for those sit in attributes. How those default values should be specified is (especially at the beginning) not entirely intuitive. The documentation helps, but you can also enable support and then adjust values to your desired ones directly in the editor. Then, if you take a look at the generated code, you will see something like this:

<!-- wp:fse-starter/skewed-section {"align":"full","style":{"spacing":{"margin":{"top":"var:preset|spacing|50","bottom":"var:preset|spacing|50"}}},"name":"fse-starter/skewed-section","data":{"top_skew":"-3","_top_skew":"field_65b14331f4cdc","bottom_skew":"3","_bottom_skew":"field_65b14396f4cdd","background_color":"#abb8c3","_background_color":"field_65b226b8ed7f3"},"mode":"preview"} -->

See the align and style? Those are the keys that you will use in the attributes field. The values (so "full" and {"margin":{"top":"var:preset|spacing|50", "bottom":"var:preset|spacing|50"}}, respectively) are what will go into the default value. So now, all you need to do is figure out the type, typically a string or an object.

Our full block.json looks like this:

{
  "$schema": "<https://schemas.wp.org/trunk/block.json>",
  "apiVersion": 3,
  "name": "fse-starter/skewed-section",
  "title": "Skewed section",
  "category": "design",
  "icon": "<svg width='100%' height='100%' viewBox='0 0 512 512' version='1.1' xmlns='<http://www.w3.org/2000/svg>'><path d='M60,460l0,-400l400,100l0,200l-400,100Z'/></svg>",
  "description": "Section with top/bottom skew specified in degrees.",
  "keywords": [ "section" ],
  "version": "1.0.0",
  "textdomain": "fse-starter",
  "style": [ "block-skewed-section" ],
  "acf": {
    "mode": "preview",
    "renderTemplate": "render.php",
    "postTypes": [ "post", "page" ],
    "blockVersion": 2
  },
  "supports": {
    "align": [ "wide", "full" ],
    "anchor": true,
    "spacing": {
      "margin": true
    }
  },
  "attributes": {
    "align": {
      "type": "string",
      "default": "full"
    },
    "style": {
      "type": "object",
      "default": {
        "spacing": {
          "margin": {
            "top": "var:preset|spacing|50",
            "bottom": "var:preset|spacing|50"
          }
        }
      }
    }
  }
}

render.php

Now, it’s the place where stuff actually happens, AKA render.php. If you ignore the $template and all the comments, it is actually quite small 😉.

 <?php

// Default attributes from block.json
// id is a special case in case user specified the anchor:
$block_id = $block['id'];
if ( isset( $block['anchor'] ) && ! empty( $block['anchor'] ) ) {
	$block_id = $block['anchor'];
}

// To get all of the supported attributes ready for printing, you can use
// <https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/>
// but keep <https://www.advancedcustomfields.com/resources/acf-blocks-using-get_block_wrapper_attributes/> in mind
// TL;DR: only add supported attributes to block.json
// if they can be added to the top-level wrapper
$wrapper_attributes = get_block_wrapper_attributes();

// Custom attributes set in ACF
$top_skew = get_field('top_skew');
$bottom_skew = get_field('bottom_skew');
$background_color = get_field('background_color');

// $root_class holds a name WP will generate for us
// based on the "name" field in block.json
$root_class = 'wp-block-fse-starter-skewed-section';

// This is the whole point of creating this block ;)
// We will use it in the "style" attr later
$content_style = sprintf('background-color: %s; --top-skew: %ddeg; --bottom-skew: %ddeg; --top-skew-sign: %d; --bottom-skew-sign: %d;',
	$background_color, $top_skew, $bottom_skew, $top_skew < 0 ? -1 : 1, $bottom_skew < 0 ? -1 : 1);

// Default content template
// You can create it in Gutenberg and then copy the HTML from the code editor
// and use <https://happyprime.github.io/wphtml-converter/>
// to convert it to PHP later
$template = array(
	array(
		'core/group',
		array(
			'style' => array(
				'spacing' => array(
					'blockGap' => 'var:preset|spacing|30',
					'padding' => array(
						'top' => 'var:preset|spacing|50',
						'bottom' => 'var:preset|spacing|50',
					)
				)
			),
			'layout' => array(
				'type' => 'constrained',
				'orientation' => 'vertical',
				'justifyContent' => 'center',
				'verticalAlignment' => 'center',
				'flexWrap' => 'nowrap',
			)
		),
		array(
			array(
				'core/heading',
				array(
					'textAlign' => 'center',
					'placeholder' => 'Title Goes Here',
				),
				array()
			),
			array(
				'core/paragraph',
				array(
					'align' => 'center',
					'placeholder' => 'Paragraph text goes here',
				),
				array()
			),
		)
	),
);

?>
<?php if ( ! $is_preview ) {
    // We output this div only on the front end. In the editor,
    // it is added automatically by WordPress and will have
    // all "native" settings (like margins, padding, colors, fonts
    // and whatever else block supports) already applied.
?>
    <div id="<?php echo esc_attr( $block_id ); ?>" <?php echo $wrapper_attributes; ?>>
<?php } ?>

<div
	class="<?php echo esc_attr(sprintf('%s__content', $root_class)); ?>"
	style="<?php echo esc_attr($content_style); ?>">
	<InnerBlocks template="<?php echo esc_attr( wp_json_encode( $template ) ); ?>" />
</div>

<?php if ( ! $is_preview ) { ?>
    </div>
<?php } ?>

Let’s focus on the very core of the block:

<?php

$content_style = sprintf('background-color: %s; --top-skew: %ddeg; --bottom-skew: %ddeg; --top-skew-sign: %d; --bottom-skew-sign: %d;',
	$background_color, $top_skew, $bottom_skew, $top_skew < 0 ? -1 : 1, $bottom_skew < 0 ? -1 : 1);

// ...
?>

<div
	class="<?php echo esc_attr(sprintf('%s__content', $root_class)); ?>"
	style="<?php echo esc_attr($content_style); ?>">
	<InnerBlocks template="<?php echo esc_attr( wp_json_encode( $template ) ); ?>" />
</div>

See those --top-skew/--bottom-skew/etc. CSS properties I’m creating? As far as I know, there’s no way to add them to the outermost wrapper unless you write a custom Gutenberg block using Block API. We could add them to that conditionally outputted div (e.g., passing them to get_block_wrapper_attributes function), but then the preview in the editor would not work correctly. Therefore, I created an extra inner div to set them safely.

Remember that suspicious background color field? The existence of this content div is the answer – I need the background to be set on the same element as properties for the CSS to work correctly. So, I couldn’t use the “native” background color setting since it would get applied to the outer wrapper, and the extra ACF field was added instead. The main drawback here is that ACF color fields are independent of the WP ones, which means that e.g., predefined color palettes and gradients are unavailable 😥.

💡 This case highlights a vital rule of thumb when working with ACF Blocks: only use “native” Block API attributes that can be applied to the outermost HTML tag. Otherwise, the editor experience will be broken ☠️, and/or you will get inconsistent behavior between the editor and the end result visible to the user.

One of the very important features of this block is the <InnerBlocks ...> tag usage. Since our block can be potentially reused multiple times throughout the page and has varying content, there is no point in trying to account for all possibilities via many extra fields. It is better just to allow the user to insert whichever blocks he needs. The starting point template contains only a heading and a paragraph but is fully customizable. In the InnerBlocks documentation, you will also find examples of restricting allowed blocks (it’s possible only to allow other ACF Blocks, too 🤩), locking the template, etc.

style.css

And finally, we have the style.css, which contents are left as the exercise for the reader as this is not the focus of this entry 😜.

.wp-block-fse-starter-skewed-section__content {
  /* match those to defaults set in ACF for consistency */
  --top-skew: -3deg;
  --bottom-skew: 3deg;

  /* ideally, we would just use sign() CSS function, but support is poor so far */
  --top-skew-sign: -1;
  --bottom-skew-sign: 1;

  --top-skew-height: calc(tan(var(--top-skew) * var(--top-skew-sign)) * 100vw);
  --bottom-skew-height: calc(tan(var(--bottom-skew) * var(--bottom-skew-sign)) * 100vw);

  --top-left: calc(min(var(--top-skew-sign), 0) * -1 * var(--top-skew-height));
  --top-right: calc(max(var(--top-skew-sign), 0) * var(--top-skew-height));
  --bottom-left: calc(100% - max(var(--bottom-skew-sign), 0) * var(--bottom-skew-height));
  --bottom-right: calc(100% - min(var(--bottom-skew-sign), 0) * -1 * var(--bottom-skew-height));

  border-top: var(--top-skew-height) solid transparent;
  border-bottom: var(--bottom-skew-height) solid transparent;
  clip-path: border-box polygon(0 var(--top-left), 100% var(--top-right), 100% var(--bottom-right), 0 var(--bottom-left));
}

Code

Since you got that far 🥰 here are the files:

fse-starter.zip

It contains an absolutely minimal theme (it really should have been called fse-mwe, not fse-starter 🙈) with the awesome skewed-section block 😉.

Homework 🎓

  1. Add a background image for the section.
  2. Send DM to @ula on Slack with the feedback and/or questions.

Resources

Troubleshooting

Help, my block is not visible in the inserter/ACF dropdown/anywhere!

The block’s registration code often caches the list of available blocks. If you add/remove/rename a block, you must bump the theme version number in the theme-name/style.css file.

Are you planning to use ACF with your next project and you afraid to do it? Don’t hesitate to reach out to us.

Related posts

Contact us 👋