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:
- Bootstrap 4 as a base for our CSS
- Laravel Blade for templating
- Npm and Webpack for managing frontend dependencies
- Yarn
- ES6 Javascript
- Restructured theme layout
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:
- WordPress >= 5.6
- PHP >= 7.4 (with php-mbstring enabled)
- Composer
- Node.js >= 10
- Yarn
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
- Run
yarn
from the theme directory to install dependencies - Update
resources/assets/config.json
settings: -
devUrl
should reflect your local development hostname -
publicPath
should reflect your WordPress folder structure (/wp-content/themes/theme-name
for non-Bedrock installs)
Build commands
-
yarn start
— Compile assets when file changes are made, start Browsersync session -
yarn build
— Compile and optimize the files in your assets directory -
yarn build:production
— Compile assets for production
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:
This is how the usage of a template will look:
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!