BuiltByCactus

Engineering Handbook

Themes

At Built By Cactus we like to be at the head of the pack and that means our custom built WordPress themes need to be both performant and easy to extend/maintain.

To do this, we always work on top of the Roots Sage Theme.

As of the latest version (Sage 9) this includes the following components that we use:

One thing that Sage ships with that we won’t stick to is the PSR-2 coding standards. Whilst this is a decision they’ve made, we prefer to stick by our coding standards.

Full Sage 9 documentation can be found here

Requirements

We require the following dependencies when working with WordPress themes:

Installation

See the Sage documentation for full installation and set-up details, but it should be as simple as running the following:

# @ app/themes/ or wp-content/themes/

$ composer create-project roots/sage your-theme-name

Plugins we use

Whilst no plugin is suitable for every site, here are a few that we use quite often as they make our life easier.

Advanced Custom Fields Pro

We have a licence for the Pro version of ACF. It means less faffing around with meta blocks and a simpler build for our admin area.

Make sure you make use of the Local JSON feature as so ACF Field Groups are stored in GIT.

We have more info here.

Yoast SEO

This plugin is the best SEO plugin out there. That’s all we need to say about it.

Theme Structure

themes/your-theme-name/   # → Root of your Sage based theme
├── app/                  # → Theme PHP
│   ├── controllers/      # → Controller files
│   ├── admin.php         # → Theme customizer setup
│   ├── filters.php       # → Theme filters
│   ├── helpers.php       # → Helper functions
│   └── setup.php         # → Theme setup
├── composer.json         # → Autoloading for `app/` files
├── composer.lock         # → Composer lock file (never edit)
├── dist/                 # → Built theme assets (never edit)
├── node_modules/         # → Node.js packages (never edit)
├── package.json          # → Node.js dependencies and scripts
├── resources/            # → Theme assets and templates
│   ├── acf-json/         # → ACF saved JSON fields (autoupdated)
│   ├── assets/           # → Front-end assets
│   │   ├── config.json   # → Settings for compiled assets
│   │   ├── build/        # → Webpack and ESLint config
│   │   ├── fonts/        # → Theme fonts
│   │   ├── images/       # → Theme images
│   │   ├── scripts/      # → Theme JS
│   │   └── styles/       # → Theme stylesheets
│   ├── functions.php     # → Composer autoloader, theme includes
│   ├── index.php         # → Never manually edit
│   ├── screenshot.png    # → Theme screenshot for WP admin
│   ├── style.css         # → Theme meta information
│   └── views/            # → Theme templates
│       ├── layouts/      # → Base templates
│       ├── blocks/       # → Block templates
│       └── partials/     # → Partial templates
└── vendor/               # → Composer packages (never edit)

Theme development

Build commands

Blocks

At Built By Cactus we like code to be as DRY as possible, therefore we use a block structure in our themes. Using blocks enables us to repeat code as little as possible when developing.

ACF

When setting up blocks, we should add our ACF fields into an inactive field group prefixed with [Template].

This allows us to keep the actual field groups and our block groups separate and means if we need to change a block, we have a single place to do it.

These template field groups can then be pulled in to an active field group by using a clone field.

You can either clone as is, so the fields will hold the same name field = text or prefix it with the clone field name, which is useful if using that block more than once on a page field = header_text.

See below for an example of a template field group:

Template field group example
Template field group example

This is how the usage of a template will look:

Usage of a template field group example
Usage of a template field group example

PHP

PHP When writing blocks in PHP we don’t access any of our ACF fields in the template and instead prefer for them to be passed in as arguments. This way if we need to pass in hard coded text we can easily do that.

We should check for arguments at the top of the file and set a sensible default if they’re not pre-set. A general block should look something like this:

<?php
$text     = $text ?? '';
$image    = $image ?? '';
$date     = $date ?? false;
$modifier = $modifier ?? '';
?>

<div class="image-block d-flex align-items-center {{ $modifier }}" style="background-image: url('{{ $image }}')">
    <div class="image-block__text align-self-end">
        <div class="container">
            <div class="row">
                @if($date)
                    <div class="col-12 image-block__date">
                        {{ $date }}
                    </div>
                @endif

                <div class="col-12">
                    {{ $text }}
                </div>
            </div>
        </div>
    </div>
</div>

We can use a block as follows:

@include('blocks.header', [
    'text' => get_field('header_text'),
])

or we can pass through more arguments

@include('blocks.header', [
    'text' => get_field('header_text'),
    'title' => 'Home',
    'modifier' => 'header-block--light'
])

Writing our PHP in this way means that we should be as DRY as possible and speed up development in the long run!