🔵 other: Rename project and publish

This commit is contained in:
Keith Solomon
2026-07-29 14:25:19 -05:00
parent 5512918b0f
commit 7dbb991fb9
70 changed files with 558 additions and 5386 deletions
+100 -76
View File
@@ -1,40 +1,55 @@
# Creating Blocks in VDI-Starter-v5
# Creating Blocks in SoloFrame Evo
## 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)
- [ACF Field Groups](#acf-field-groups)
- [Parent-Child Block Patterns (InnerBlocks)](#parent-child-block-patterns-innerblocks)
- [Tailwind CSS in Blocks](#tailwind-css-in-blocks)
- [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 ACF Field Groups in WordPress Admin](#5-create-acf-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)
- [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
VDI-Starter-v5 uses Advanced Custom Fields (ACF) blocks to build page content. ACF blocks are a type of WordPress Gutenberg block where the editing interface comes from 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.
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.
@@ -74,9 +89,9 @@ Here is what happens:
## Block Anatomy: The Three-File Pattern
Every ACF block in this theme consists of exactly three files inside `views/blocks/{block-name}/`:
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
@@ -97,15 +112,15 @@ The naming convention is consistent: the directory name, the PHP file, and the C
### block.json -- The Registration Manifest
The `block.json` file tells WordPress and ACF everything they need to know about the block. Here is the boilerplate version:
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 ACF blocks.",
"description": "Boilerplate code to create SCF/ACF blocks.",
"style": ["file:./boilerplate.css"],
"category": "vdi-blocks",
"category": "sf-blocks",
"icon": "block-default",
"keywords": ["boilerplate"],
"acf": {
@@ -127,13 +142,13 @@ The `block.json` file tells WordPress and ACF everything they need to know about
**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 `vdi-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 https://developer.wordpress.org/resource/dashicons/. |
| `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. |
@@ -150,10 +165,10 @@ The `block.json` file tells WordPress and ACF everything they need to know about
```json
{
"name": "acf/buttons",
"title": "Buttons (VDI)",
"title": "Buttons",
"description": "A button or group of buttons.",
"allowedBlocks": ["acf/button"],
"category": "vdi-blocks",
"category": "sf-blocks",
...
}
```
@@ -169,10 +184,10 @@ The PHP template is responsible for outputting the block's HTML. Every template
*
* This is the template for building your own custom blocks.
*
* @package BasicWP
* @package SoloFrameEvo
*/
namespace BasicWP;
namespace SoloFrameEvo;
$classes = 'boilerplate';
@@ -190,7 +205,7 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
**Key elements explained:**
1. **`namespace BasicWP;`** -- 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.
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.
@@ -214,7 +229,7 @@ The CSS filename must match the block slug and be referenced in `block.json` usi
"style": ["file:./testimonial.css"]
```
You can use Tailwind utility classes directly in your PHP templates (e.g., `class="flex gap-4 p-6"`), and they will work as long as the Tailwind build process can detect them. For complex or block-specific styles that are not expressible as utility classes, write them in the block's CSS file using the BEM-like naming convention:
You can use Tailwind utility classes directly in your PHP templates (e.g., `class="flex gap-4 p-6"`), and they will work as long as the Tailwind build process can detect them. For complex or block-specific styles that are not expressible as utility classes, write them in the block's CSS file. You can also use Tailwind's `@apply` directive in these CSS files to compose utility classes into reusable styles:
```css
/* testimonial.css */
@@ -238,7 +253,7 @@ The file can be empty initially and filled in as needed.
## Helper Functions
The theme provides several helper functions in `lib/helpers.php`, all under the `BasicWP` namespace. Because every block template declares `namespace BasicWP;`, you can call these functions directly without any prefix.
The theme provides several helper functions in `lib/helpers.php`, all under the `SoloFrameEvo` namespace. Because every block template declares `namespace SoloFrameEvo;`, you can call these functions directly without any prefix.
### blockWrapperAttributes()
@@ -249,12 +264,14 @@ function blockWrapperAttributes( $classes, $is_preview )
**Purpose:** Generates the HTML attributes string for a block's root element, handling the difference between the editor and the frontend.
**Parameters:**
- `$classes` (string) -- A space-separated list of CSS class names to apply to the block wrapper.
- `$is_preview` (bool) -- Whether the block is being rendered in the editor. Always pass the global `$is_preview` variable.
**Returns:** A string of HTML attributes ready to echo inside an HTML tag.
**How it works:**
- When `$is_preview` is `true` (in the editor), it returns `class="my-class"`. This is a simplified output that avoids rendering issues caused by WordPress's `get_block_wrapper_attributes()` in the editor context.
- When `$is_preview` is `false` (on the frontend), it calls WordPress's `get_block_wrapper_attributes()` with your classes merged in, producing the full set of attributes including alignment classes, anchor IDs, custom class names from the editor, and more.
@@ -280,6 +297,7 @@ function getFieldValue( $field_path )
**Purpose:** Retrieves nested values from ACF option fields (Global Fields) using dot notation.
**Parameters:**
- `$field_path` (string) -- A dot-notated path to the value. For example, `'contact_info.phone'` retrieves the `phone` subfield from the `contact_info` options page field.
**Returns:** The value at the specified path, or an empty string if the path does not exist.
@@ -314,7 +332,7 @@ echo wp_kses( $video_embed_html, escEmbeds() );
---
## ACF Field Groups
## SCF/ACF Field Groups
After creating your block's three files, you need to create an ACF field group in the WordPress admin. This defines the fields that editors fill in when editing the block.
@@ -331,12 +349,12 @@ After creating your block's three files, you need to create an ACF field group i
- **Repeater** -- Repeatable groups of fields (for lists, slides, etc.)
- **Link** -- URL + title + target picker
- **WYSIWYG** -- Rich text editor
4. Set the **location rule** to: **Block > is equal to > [Your Block Name]**. This tells ACF to show these fields when editing your block.
4. Set the **location rule** to: **Block > is equal to > [Your Block Name]**. This tells SCF/ACF to show these fields when editing your block.
5. Click **Save** or **Publish**.
### JSON Sync
The theme's `ACF` class (in `lib/class-acf.php`) configures custom save and load paths for ACF JSON:
The theme's `ACF` class (in `lib/class-acf.php`) configures custom save and load paths for SCF/ACF JSON:
```php
class ACF {
@@ -359,6 +377,7 @@ class ACF {
```
This means:
- When you save a field group in the admin, ACF writes a JSON file to the `acf/` directory in the theme root.
- When ACF loads field groups, it reads from the same `acf/` directory.
- These JSON files are version-controlled, so field group configurations travel with the codebase and sync across environments.
@@ -369,7 +388,7 @@ This means:
## Parent-Child Block Patterns (InnerBlocks)
Some blocks act as containers that hold other blocks. WordPress provides `<InnerBlocks />` for this purpose, and ACF blocks can use it too.
Some blocks act as containers that hold other blocks. WordPress provides `<InnerBlocks />` for this purpose, and SCF/ACF blocks can use it too.
### Basic InnerBlocks
@@ -506,7 +525,7 @@ To prevent this, add editor-only classes to `whitelist.php`. This file contains
<!-- ... more classes ... -->
```
The whitelist is primarily used for grid and layout classes that the Grid block applies dynamically through ACF field values (since those class names are generated at runtime, not hardcoded in templates).
The whitelist is primarily used for grid and layout classes that the Grid block applies dynamically through SCF/ACF field values (since those class names are generated at runtime, not hardcoded in templates).
### Block-Specific CSS Files
@@ -524,7 +543,7 @@ This walkthrough demonstrates creating a "Testimonial" block from scratch.
Create a new folder under `views/blocks/` using a lowercase, hyphenated slug:
```
```plain
views/blocks/testimonial/
```
@@ -538,9 +557,9 @@ Create `views/blocks/testimonial/block.json`:
{
"name": "acf/testimonial",
"title": "Testimonial",
"description": "A customer testimonial with quote, name, and role.",
"description": "A customer testimonial with quote, name, image, and role.",
"style": ["file:./testimonial.css"],
"category": "vdi-blocks",
"category": "sf-blocks",
"icon": "format-quote",
"keywords": ["testimonial", "quote", "review"],
"acf": {
@@ -560,8 +579,9 @@ Create `views/blocks/testimonial/block.json`:
```
**Checklist for `block.json`:**
- `name` starts with `acf/`
- `category` is set to `vdi-blocks`
- `category` is set to `sf-blocks`
- `style` references the CSS file with the `file:./` prefix
- `acf.renderTemplate` matches the PHP filename exactly
- `supports.html` is `false` (ACF blocks should not support HTML editing)
@@ -578,10 +598,10 @@ Create `views/blocks/testimonial/testimonial.php`:
*
* A customer testimonial with quote, name, and role.
*
* @package BasicWP
* @package SoloFrameEvo
*/
namespace BasicWP;
namespace SoloFrameEvo;
$classes = 'testimonial';
$wrapper = blockWrapperAttributes( $classes, $is_preview );
@@ -618,14 +638,14 @@ $image = get_field( 'image' );
```
**Checklist for the PHP template:**
- Always start with `namespace BasicWP;`
- Always start with `namespace SoloFrameEvo;`
- Always call `blockWrapperAttributes( $classes, $is_preview )` and assign it to `$wrapper`
- Always echo `$wrapper` inside the root element with `wp_kses_post()`
- Use `get_field()` to retrieve ACF field values
- Use `getFieldValue()` for nested option fields
- Escape all output: `wp_kses_post()` for HTML content, `esc_html()` for plain text, `esc_url()` for URLs, `esc_attr()` for HTML attributes
- Use semantic HTML elements (`<section>`, `<blockquote>`, `<cite>`, etc.)
- Follow BEM-like naming for CSS classes: `.block`, `.block__element`, `.block__element--modifier`
### 4. Create the CSS File
@@ -665,7 +685,7 @@ Create `views/blocks/testimonial/testimonial.css`. It can start empty or with ba
If you are using Tailwind utility classes in the PHP template, you may not need much custom CSS. The file still needs to exist and be referenced in `block.json` so WordPress can load it.
### 5. Create ACF Field Groups in WordPress Admin
### 5. Create SCF/ACF Field Groups in WordPress Admin
1. Log in to the WordPress admin dashboard.
2. Go to **Custom Fields > Add New**.
@@ -673,7 +693,7 @@ If you are using Tailwind utility classes in the PHP template, you may not need
4. Add the following fields:
| Field Label | Field Name | Field Type | Notes |
|---|---|---|---|
| --- | --- | --- | --- |
| Quote | `quote` | Textarea | The testimonial text |
| Name | `name` | Text | The customer's name |
| Role | `role` | Text | The customer's role or title (optional) |
@@ -695,7 +715,7 @@ npm run build
Then verify the block appears in the editor:
1. Edit a page in the WordPress block editor.
2. Open the inserter and look under **VDI Custom Blocks**.
2. Open the inserter and look under **SoloFrame Custom Blocks**.
3. You should see "Testimonial" with the quote icon.
4. Insert the block and fill in the fields.
5. Save and preview the page on the frontend to confirm rendering works correctly.
@@ -709,7 +729,8 @@ Then verify the block appears in the editor:
The Homepage Hero is a straightforward block that retrieves ACF fields and renders them with Tailwind classes. It does not use InnerBlocks.
**Key patterns:**
- Retrieves multiple ACF fields with `get_field()`
- Retrieves multiple SCF/ACF fields with `get_field()`
- Conditionally renders sections only when fields have values (`! empty( $heading )`)
- Uses Tailwind classes extensively for layout and styling
- Handles editor vs. frontend differences for link URLs
@@ -740,9 +761,10 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
### Parent Block with InnerBlocks: Section
The Section block is a container that wraps its child blocks with optional background styling. It demonstrates conditional rendering based on ACF fields.
The Section block is a container that wraps its child blocks with optional background styling. It demonstrates conditional rendering based on SCF/ACF fields.
**Key patterns:**
- Builds CSS class strings dynamically based on field values
- Builds inline `style` strings from field values
- Conditionally renders an overlay div
@@ -801,6 +823,7 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
The Buttons block is a container that only allows Button blocks as children. It enforces this restriction through both `block.json` and the template.
**Key patterns:**
- Uses `allowedBlocks` in `block.json` to restrict children to `acf/button`
- Sets `"jsx": true` in `supports` to enable InnerBlocks
- Passes Tailwind classes to InnerBlocks via the `className` prop
@@ -835,6 +858,7 @@ Note: The Buttons block uses `esc_attr()` instead of `wp_kses_post()` for the wr
The Grid block builds CSS classes dynamically from ACF field values (columns, breakpoints, gaps). This is a case where runtime-generated class names need to be whitelisted.
**Key patterns:**
- Dynamically constructs Tailwind class names from field values (e.g., `'grid-cols-' . get_field( 'columns' )`)
- Uses `$block['anchor']` and `$block['className']` for editor-set attributes
- These dynamic class names are added to `whitelist.php` so Tailwind includes them in the build
@@ -873,27 +897,27 @@ Because the class names like `grid-cols-3` and `md:grid-cols-4` are generated at
The Contact Info block demonstrates how to access ACF options page data (Global Fields) using `getFieldValue()`.
**Key patterns:**
- Uses `get_field( 'contact_info', 'option' )` to retrieve the options page field group, then accesses sub-fields with array syntax
- Alternatively, could use `getFieldValue( 'contact_info.phone' )` for the same result
- Uses `getFieldValue( 'contact_info.phone' )` to retrieve the options page field group, then accesses sub-fields with array syntax
- Combines static content from Global Fields with dynamic InnerBlocks content (a contact form)
```php
namespace BasicWP;
namespace SoloFrameEvo;
$classes = 'contact-info';
$wrapper = blockWrapperAttributes( $classes, $is_preview );
?>
<section <?php echo esc_attr( $wrapper ); ?>>
<section <?php echo wp_kses_post( $wrapper ); ?>>
<div class="flex flex-col lg:flex-row">
<div class="w-full lg:w-1/2 p-6">
<h2 class="text-2xl font-bold mb-4">Contact Information</h2>
<p><?php echo wp_kses_post( get_field( 'contact_info', 'option' )['address'] ); ?></p>
<p><a href="mailto:<?php echo esc_html( get_field( 'contact_info', 'option' )['email'] ); ?>">
<?php echo esc_html( get_field( 'contact_info', 'option' )['email'] ); ?>
<p><?php echo wp_kses_post( getFieldValue( 'contact_info.address' ) ); ?></p>
<p><a href="mailto:<?php echo esc_html( getFieldValue( 'contact_info.email' ) ); ?>">
<?php echo esc_html( getFieldValue( 'contact_info.email' ) ); ?>
</a></p>
<p><a href="tel:<?php echo esc_html( get_field( 'contact_info', 'option' )['phone'] ); ?>">
<?php echo esc_html( get_field( 'contact_info', 'option' )['phone'] ); ?>
<p><a href="tel:<?php echo esc_html( getFieldValue( 'contact_info.phone' ) ); ?>">
<?php echo esc_html( getFieldValue( 'contact_info.phone' ) ); ?>
</a></p>
</div>
@@ -910,12 +934,12 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
### Do
- **Always use `namespace BasicWP;`** at the top of every block PHP template. Without it, helper functions like `blockWrapperAttributes()` and `getFieldValue()` will not be available.
- **Always use `namespace SoloFrameEvo;`** at the top of every block PHP template. Without it, helper functions like `blockWrapperAttributes()` and `getFieldValue()` will not be available.
- **Always use `blockWrapperAttributes()`** for the root element's attributes. Never call `get_block_wrapper_attributes()` directly.
- **Always escape output.** Use `wp_kses_post()` for HTML content, `esc_html()` for plain text, `esc_url()` for URLs, and `esc_attr()` for HTML attribute values.
- **Always set `category` to `vdi-blocks`** in `block.json` so your block appears under "VDI Custom Blocks" in the editor.
- **Always set `category` to `sf-blocks`** in `block.json` so your block appears under "VDI Custom Blocks" in the editor.
- **Always prefix `name` with `acf/`** in `block.json` (e.g., `"acf/testimonial"`, not just `"testimonial"`).
- **Always set `supports.html` to `false`** in `block.json` for ACF blocks. ACF blocks use PHP templates, not HTML editing.
- **Always set `supports.html` to `false`** in `block.json` for SCF/ACF blocks. SCF/ACF blocks use PHP templates, not HTML editing.
- **Always set `supports.jsx` to `true`** if your block uses `<InnerBlocks />`. Without this, the InnerBlocks area will not render.
- **Commit ACF JSON files** from the `acf/` directory to version control after creating field groups.
- **Use semantic HTML elements** as block wrappers (`<section>`, `<article>`, `<aside>`, `<nav>`, etc.) instead of generic `<div>` elements where appropriate.
@@ -929,7 +953,7 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
- **Do not forget to create the CSS file** referenced in `block.json`. Even if the file is empty, WordPress needs it to exist. If the file is missing, WordPress may throw an error when loading the block.
- **Do not use `'option'` directly with `get_field()` for nested values without null checking.** Prefer `getFieldValue()` which handles missing values gracefully.
- **Do not set `supports.multiple` to `false`** unless the block truly must be unique per page (like a homepage hero). Most blocks should allow multiple instances.
- **Do not hardcode editor-only Tailwind classes in PHP templates** without adding them to `whitelist.php`. If a class only appears in the editor's UI (like grid column classes set via ACF fields), Tailwind will not include it in the build.
- **Do not hardcode editor-only Tailwind classes in PHP templates** without adding them to `whitelist.php`. If a class only appears in the editor's UI (like grid column classes set via SCF/ACF fields), Tailwind will not include it in the build.
- **Do not use the `style` attribute on the block's root element alongside `blockWrapperAttributes()` for background colors** unless the block specifically needs it. WordPress's built-in color supports (enabled via `supports.color`) handle this automatically.
### Debugging Tips
@@ -938,4 +962,4 @@ $wrapper = blockWrapperAttributes( $classes, $is_preview );
- If ACF fields do not show up when editing a block, verify the field group's location rule is set to "Block is equal to [Your Block Name]".
- If styles are not loading, confirm the `style` array in `block.json` uses the `file:./` prefix and the CSS filename matches exactly.
- If Tailwind classes are not applying on the frontend, run `npm run build` and check that the classes are either in your templates or in `whitelist.php`.
- If InnerBlocks are not rendering, confirm `"jsx": true` is set in the block's `supports` configuration.
- If InnerBlocks are not rendering, confirm `"jsx": true` is set in the block's `supports` configuration.