# Creating Blocks in SoloFrame Evo ## Table of Contents - [Creating Blocks in SoloFrame Evo](#creating-blocks-in-soloframe-evo) - [Table of Contents](#table-of-contents) - [Overview](#overview) - [How Block Registration Works](#how-block-registration-works) - [Block Anatomy: The Three-File Pattern](#block-anatomy-the-three-file-pattern) - [block.json -- The Registration Manifest](#blockjson----the-registration-manifest) - [The PHP Template -- Rendering the Block](#the-php-template----rendering-the-block) - [The CSS File -- Scoped Styles](#the-css-file----scoped-styles) - [Helper Functions](#helper-functions) - [blockWrapperAttributes()](#blockwrapperattributes) - [getFieldValue()](#getfieldvalue) - [escEmbeds()](#escembeds) - [SCF/ACF Field Groups](#scfacf-field-groups) - [Creating a Field Group](#creating-a-field-group) - [JSON Sync](#json-sync) - [Parent-Child Block Patterns (InnerBlocks)](#parent-child-block-patterns-innerblocks) - [Basic InnerBlocks](#basic-innerblocks) - [Restricted InnerBlocks](#restricted-innerblocks) - [Enabling InnerBlocks in block.json](#enabling-innerblocks-in-blockjson) - [Adding Classes to InnerBlocks](#adding-classes-to-innerblocks) - [Tailwind CSS in Blocks](#tailwind-css-in-blocks) - [How Tailwind is Set Up](#how-tailwind-is-set-up) - [Using Tailwind Classes in Blocks](#using-tailwind-classes-in-blocks) - [Whitelisting Editor-Only Classes](#whitelisting-editor-only-classes) - [Block-Specific CSS Files](#block-specific-css-files) - [Step-by-Step: Creating a New Block](#step-by-step-creating-a-new-block) - [1. Create the Block Directory](#1-create-the-block-directory) - [2. Create block.json](#2-create-blockjson) - [3. Create the PHP Template](#3-create-the-php-template) - [4. Create the CSS File](#4-create-the-css-file) - [5. Create SCF/ACF Field Groups in WordPress Admin](#5-create-scfacf-field-groups-in-wordpress-admin) - [6. Build and Verify](#6-build-and-verify) - [Real-World Examples from This Theme](#real-world-examples-from-this-theme) - [Simple Block: Homepage Hero](#simple-block-homepage-hero) - [Parent Block with InnerBlocks: Section](#parent-block-with-innerblocks-section) - [Restricted Parent Block: Buttons](#restricted-parent-block-buttons) - [Dynamic Parent Block: Grid](#dynamic-parent-block-grid) - [Block Using Global Fields: Contact Info](#block-using-global-fields-contact-info) - [Common Pitfalls and Best Practices](#common-pitfalls-and-best-practices) - [Do](#do) - [Do Not](#do-not) - [Debugging Tips](#debugging-tips) --- ## Overview SoloFrame Evo uses Secure Custom Fields (SCF) or Advanced Custom Fields (ACF) blocks to build page content. SCF/ACF blocks are a type of WordPress Gutenberg block where the editing interface comes from SCF/ACF field groups and the rendering is handled by a PHP template (instead of React). This approach lets you build rich, structured content blocks using familiar PHP templating and Tailwind CSS, without writing JavaScript. Every block in this theme follows the same three-file pattern inside `views/blocks/{block-name}/`, and new blocks are automatically discovered and registered -- no manual registration required. --- ## How Block Registration Works Block registration is handled by the `regACFBlocks()` function in `functions.php`. You never need to register a block manually; the function handles discovery for you. ```php // functions.php function regACFBlocks() { define( 'BLOCKS_DIR', get_stylesheet_directory() . '/views/blocks' ); if ( is_dir( BLOCKS_DIR ) ) { foreach ( scandir( BLOCKS_DIR ) as $folder ) { if ( ( '.' !== $folder && '..' !== $folder && 'boilerplate' !== $folder ) && is_dir( BLOCKS_DIR . '/' . $folder ) ) { register_block_type( BLOCKS_DIR . '/' . $folder ); } } } } add_action( 'init', __NAMESPACE__ . '\\regACFBlocks', 5 ); ``` Here is what happens: 1. `BLOCKS_DIR` is defined as `get_stylesheet_directory() . '/views/blocks'`, pointing to the `views/blocks/` directory inside the theme. 2. The function scans every subdirectory inside `views/blocks/`. 3. It skips `.` (current dir), `..` (parent dir), and the `boilerplate` directory (the boilerplate is a template for creating new blocks, not a real block). 4. For every remaining directory, it calls WordPress's `register_block_type()`, which reads the `block.json` file inside that directory and registers the block. 5. The function runs on the `init` hook at priority 5, ensuring blocks are registered before the editor needs them. **What this means for you:** To create a new block, you only need to add a new subdirectory under `views/blocks/` with a valid `block.json` file. WordPress discovers and registers it automatically the next time the theme loads. --- ## Block Anatomy: The Three-File Pattern Every SCF/ACF block in this theme consists of exactly three files inside `views/blocks/{block-name}/`: ```plain views/blocks/ boilerplate/ <-- Template for creating new blocks (not registered) block.json boilerplate.php boilerplate.css section/ block.json section.php section.css homepage-hero/ block.json homepage-hero.php homepage-hero.css ... ``` The naming convention is consistent: the directory name, the PHP file, and the CSS file all share the same slug (e.g., `homepage-hero`). The `block.json` file always uses the literal name `block.json`. ### block.json -- The Registration Manifest The `block.json` file tells WordPress and SCF/ACF everything they need to know about the block. Here is the boilerplate version: ```json { "name": "acf/boilerplate", "title": "Block Boilerplate", "description": "Boilerplate code to create SCF/ACF blocks.", "style": ["file:./boilerplate.css"], "category": "sf-blocks", "icon": "block-default", "keywords": ["boilerplate"], "acf": { "mode": "preview", "renderTemplate": "boilerplate.php" }, "supports": { "align": true, "anchor": true, "color": true, "html": false, "jsx": false, "mode": true, "multiple": false } } ``` **Field-by-field explanation:** | Field | Purpose | | --- | --- | | `name` | The unique block identifier. **Must be prefixed with `acf/`** for ACF blocks. This becomes the machine name WordPress uses internally (e.g., `acf/testimonial`). | | `title` | The human-readable name shown in the block editor inserter (e.g., "Testimonial"). | | `description` | A short description shown in the block editor to help editors understand what the block does. | | `style` | An array of CSS files to load when this block renders. Use the `file:./` prefix for block-relative paths. WordPress only loads these stylesheets when the block is actually present on the page. | | `category` | Determines which section of the inserter the block appears under. **Always use `sf-blocks`** in this theme -- this is the custom category registered in `helpers.php` that groups all theme blocks together under "VDI Custom Blocks". | | `icon` | A Dashicon name (without the `dashicons-` prefix) shown next to the block in the inserter. Browse available icons at [DashIcons](https://developer.wordpress.org/resource/dashicons/). | | `keywords` | Additional search terms that help editors find the block in the inserter. For example, a "Testimonial" block might include `["testimonial", "quote", "review"]`. | | `acf.mode` | Controls how the block appears in the editor. `preview` shows the rendered block output; `edit` shows the ACF field inputs directly. Most blocks use `preview`. | | `acf.renderTemplate` | The PHP file that renders the block on the frontend and in preview mode. This filename must match the actual file in the directory. | | `supports.align` | Whether editors can choose alignment (left, center, right, wide, full). | | `supports.anchor` | Whether editors can set an HTML anchor (id attribute) for linking. | | `supports.color` | Whether editors can set text and background colors via the block editor. | | `supports.html` | Whether the block supports HTML editing mode in the editor. Set to `false` for ACF blocks since the template controls the markup. | | `supports.jsx` | Whether the block supports InnerBlocks (nesting other blocks inside it). Set to `true` if your block uses ``. | | `supports.mode` | Whether editors can toggle between preview and edit mode in the editor. | | `supports.multiple` | Whether the block can be inserted more than once per post. Set to `false` for blocks that should be unique (e.g., a homepage hero). | **Special field for parent blocks:** If your block restricts which blocks can be inserted as children, you can add an `allowedBlocks` key at the top level of `block.json` (not inside `supports`). The Buttons block does this: ```json { "name": "acf/buttons", "title": "Buttons", "description": "A button or group of buttons.", "allowedBlocks": ["acf/button"], "category": "sf-blocks", ... } ``` ### The PHP Template -- Rendering the Block The PHP template is responsible for outputting the block's HTML. Every template follows the same structure: ```php
>
``` **Key elements explained:** 1. **`namespace SoloFrameEvo;`** -- Every block template must declare this namespace. It gives you access to the theme's helper functions (`blockWrapperAttributes`, `getFieldValue`, etc.) without needing fully-qualified class names. 2. **`$is_preview`** -- This is a WordPress global variable that is `true` when the block is being rendered inside the block editor, and `false` on the frontend. You can use it to conditionally show editor-only content or adjust markup for the editor. 3. **`blockWrapperAttributes()`** -- This helper function (defined in `lib/helpers.php`) generates the wrapper attributes for the block's root element. It handles the difference between editor preview mode and the frontend: - In preview mode (`$is_preview` is `true`): returns a simple `class="my-class"` string, which avoids rendering issues in the editor. - On the frontend (`$is_preview` is `false`): returns the full `get_block_wrapper_attributes()` output, which includes WordPress-generated classes and attributes for alignment, anchor, custom class names, etc. **Always use `blockWrapperAttributes()` instead of calling `get_block_wrapper_attributes()` directly.** The direct call can cause rendering problems in the editor. 4. **`wp_kses_post()`** -- Always wrap the wrapper attributes output with `wp_kses_post()` for security. This sanitizes the output while preserving the HTML attributes that `blockWrapperAttributes()` generates. 5. **Semantic HTML wrapper** -- Use a semantic element like `
`, `
`, `