Architecting Scalable Full Site Editing (FSE) Block Themes and theme.json in Legacy Core PHP Implementations
Understanding the `theme.json` Foundation for FSE
The advent of Full Site Editing (FSE) in WordPress, particularly with the introduction of block themes, fundamentally shifts how themes are architected. At its core lies the `theme.json` file, a declarative configuration object that dictates global styles, settings, and block defaults. For developers migrating or building FSE-compatible themes within existing, legacy core PHP implementations, a deep understanding of `theme.json` is paramount. It acts as the single source of truth for design tokens, layout constraints, and feature flags that govern the entire site experience.
A typical `theme.json` file structures itself into several key sections: `settings`, `styles`, and `custom_settings`. The `settings` section controls editor-level configurations, such as enabling or disabling specific block features, defining custom colors, typography, and spacing scales. The `styles` section, conversely, defines the actual CSS rules applied to the front-end and editor based on these settings. `custom_settings` is a more flexible area for theme-specific configurations that don’t neatly fit into the predefined schema.
Structuring `theme.json` for Scalability and Maintainability
Scalability in FSE themes is achieved through a well-organized `theme.json`. Instead of monolithic definitions, break down styles and settings into logical groups. This not only improves readability but also facilitates easier updates and maintenance. Consider defining your color palettes, font sizes, and spacing units as reusable “tokens” within the `settings` section, which can then be referenced in the `styles` section.
For instance, defining a consistent color palette:
{
"version": 2,
"settings": {
"color": {
"palette": [
{
"name": "Primary",
"slug": "primary",
"color": "#0073aa"
},
{
"name": "Secondary",
"slug": "secondary",
"color": "#d54e21"
},
{
"name": "Dark Gray",
"slug": "dark-gray",
"color": "#333333"
},
{
"name": "Light Gray",
"slug": "light-gray",
"color": "#f0f0f0"
}
],
"custom": false,
"custom_gradient": false
},
"typography": {
"fontSizes": [
{
"name": "Small",
"slug": "small",
"size": "0.875rem"
},
{
"name": "Base",
"slug": "base",
"size": "1rem"
},
{
"name": "Large",
"slug": "large",
"size": "1.5rem"
},
{
"name": "Extra Large",
"slug": "extra-large",
"size": "2rem"
}
],
"customLineHeight": true
},
"layout": {
"contentSize": "1200px",
"wideSize": "1600px"
},
"spacing": {
"units": "rem",
"padding": {
"small": "0.5rem",
"medium": "1rem",
"large": "2rem"
},
"margin": {
"small": "0.5rem",
"medium": "1rem",
"large": "2rem"
}
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--light-gray)",
"text": "var(--wp--preset--color--dark-gray)"
},
"typography": {
"lineHeight": "1.6"
},
"blocks": {
"core/post-title": {
"color": {
"text": "var(--wp--preset--color--primary)"
},
"typography": {
"fontSize": "var(--wp--preset--font-size--extra-large)"
}
},
"core/paragraph": {
"spacing": {
"margin": {
"bottom": "var(--wp--preset--spacing--medium)"
}
}
}
}
}
}
In this example, we define a `palette` for colors, `fontSizes` for typography, `layout` constraints, and `spacing` units. These are then referenced within the `styles` section to apply them globally or to specific blocks like `core/post-title` and `core/paragraph`. This declarative approach ensures consistency across the entire site.
Integrating `theme.json` with Legacy PHP Theme Structures
The challenge in legacy PHP-based themes is how to leverage `theme.json` effectively without a complete rewrite. WordPress core handles the parsing and application of `theme.json` automatically for block themes. For hybrid themes or those still heavily reliant on PHP-driven template files and customizer settings, you need to bridge the gap.
The primary mechanism for this integration is through the `wp_enqueue_scripts` and `wp_enqueue_block_editor_assets` actions. You can hook into these to conditionally load your `theme.json` styles and settings.
Enqueuing Styles for the Front-end
To ensure your `theme.json` styles are applied to the front-end of your legacy PHP theme, you need to enqueue them. WordPress provides the `wp_get_global_styles_and_settings()` function, which retrieves the parsed `theme.json` data. You can then use this data to generate CSS or pass it to JavaScript.
/**
* Enqueue theme.json styles for the front-end.
*/
function my_legacy_theme_enqueue_global_styles() {
$global_styles = wp_get_global_styles_and_settings();
// Generate CSS from $global_styles if needed, or enqueue a pre-compiled stylesheet
// For simplicity, we'll assume a pre-compiled stylesheet is generated from theme.json
// using tools like @wordpress/style-engine or similar build processes.
// If you have a specific stylesheet generated from theme.json:
// wp_enqueue_style(
// 'my-theme-global-styles',
// get_template_directory_uri() . '/assets/css/global-styles.css',
// array(),
// filemtime( get_template_directory() . '/assets/css/global-styles.css' )
// );
// Alternatively, you can pass the data to JavaScript for dynamic styling
// or to control block-specific styles.
wp_add_inline_script(
'wp-blocks', // Or another appropriate script handle
'var myThemeGlobalStyles = ' . wp_json_encode( $global_styles ) . ';',
'before'
);
}
add_action( 'wp_enqueue_scripts', 'my_legacy_theme_enqueue_global_styles' );
In a more advanced scenario, you might use the `@wordpress/style-engine` package within your build process to compile `theme.json` into actual CSS files that can be enqueued. This is the recommended approach for production-ready themes.
Enqueuing Styles for the Block Editor
The block editor requires access to the same global styles and settings to provide a consistent editing experience. The `wp_enqueue_block_editor_assets` action is crucial here.
/**
* Enqueue theme.json styles for the block editor.
*/
function my_legacy_theme_enqueue_editor_assets() {
$global_styles = wp_get_global_styles_and_settings();
// Enqueue editor-specific styles if any
// wp_enqueue_style(
// 'my-theme-editor-styles',
// get_template_directory_uri() . '/assets/css/editor-styles.css',
// array( 'wp-edit-blocks' ),
// filemtime( get_template_directory() . '/assets/css/editor-styles.css' )
// );
// Pass global styles and settings to the editor
wp_add_inline_script(
'wp-block-editor', // Or 'wp-editor' for older versions
'var myThemeGlobalStyles = ' . wp_json_encode( $global_styles ) . ';',
'before'
);
// Ensure theme.json itself is loaded by the editor
// WordPress core usually handles this if theme.json is in the root.
// If not, you might need to explicitly load it.
// For example, by passing its content via a script.
// This is less common as core expects it in the root.
}
add_action( 'enqueue_block_editor_assets', 'my_legacy_theme_enqueue_editor_assets' );
The `wp_add_inline_script` function is vital for passing the parsed `theme.json` data to the JavaScript environment of the block editor. This allows the editor to dynamically apply styles and respect the settings defined in your `theme.json`.
Advanced Diagnostics: Troubleshooting `theme.json` Integration
When integrating `theme.json` into legacy PHP themes, several issues can arise. Effective diagnostics are key to resolving them.
1. Styles Not Applying on the Front-end
Symptom: Global styles (colors, typography, layout) defined in `theme.json` are not reflected on the public-facing website.
Diagnostic Steps:
- Verify `theme.json` Location: Ensure `theme.json` is in the root directory of your theme.
- Check Enqueue Function: Inspect your `functions.php` (or equivalent) for the `my_legacy_theme_enqueue_global_styles` function. Use `var_dump( $global_styles );` inside the function to confirm that `wp_get_global_styles_and_settings()` is returning data.
- Browser Developer Tools: Use your browser’s inspector to check the loaded stylesheets. Look for the CSS rules that should be applied. Are they missing? Are they overridden by other styles?
- CSS Specificity: If styles are present but overridden, it indicates a specificity issue. Your legacy CSS might be more specific than the generated global styles. You might need to adjust your legacy CSS or ensure your global styles are enqueued with a higher priority or are more specific.
- `wp_add_inline_script` Output: Check the page source for the inline script containing `myThemeGlobalStyles`. If it’s missing or empty, the `wp_add_inline_script` call is failing or the data isn’t being passed correctly.
- Caching: Clear all WordPress and browser caches.
2. Inconsistent Editor Experience
Symptom: The block editor does not reflect the styles or settings defined in `theme.json`. Blocks appear differently in the editor than on the front-end.
Diagnostic Steps:
- Verify Editor Enqueue: Check the `my_legacy_theme_enqueue_editor_assets` function. Ensure it’s hooked to `enqueue_block_editor_assets`.
- `wp_add_inline_script` for Editor: Confirm the inline script is being added to the editor. You can often see this by inspecting the HTML source of the editor page (though it’s dynamically loaded, so direct source inspection can be tricky; browser console logs are more reliable).
- Console Errors: Open the browser’s developer console within the block editor. Look for JavaScript errors related to styling, theme settings, or `myThemeGlobalStyles` not being defined.
- `wp_get_global_styles_and_settings()` in Editor Context: Ensure `wp_get_global_styles_and_settings()` is correctly returning data within the editor context. Sometimes, dependencies or filters might interfere.
- Block Stylesheet Conflicts: If you have custom editor stylesheets enqueued via `add_editor_style()`, ensure they don’t conflict with or override the global styles.
- WordPress Version: Ensure you are using a WordPress version that fully supports FSE and `theme.json` (typically 5.9+).
3. Custom Block Styling Issues
Symptom: Custom blocks you’ve developed do not inherit or correctly apply styles from `theme.json`.
Diagnostic Steps:
- Block Registration: Ensure your custom blocks are registered correctly using `register_block_type()`.
- `supports` Property: For blocks that should inherit styles (like color, typography, spacing), ensure the `supports` property in your block’s `block.json` (or registration arguments) is correctly configured to allow these features. For example:
{ "name": "my-plugin/my-custom-block", "title": "My Custom Block", "category": "widgets", "icon": "smiley", "apiVersion": 2, "supports": { "html": false, "align": ["wide", "full"], "color": { "background": true, "text": true }, "typography": { "fontSize": true, "lineHeight": true }, "spacing": { "padding": true, "margin": true } } } - Block Stylesheet: If your custom block has its own stylesheet, ensure it’s enqueued correctly for both the front-end and the editor.
- CSS Selectors: Verify that your block’s CSS selectors are compatible with how WordPress applies global styles. WordPress often wraps blocks in specific `wp-block-*` classes.
- `theme.json` Block Styles: If you’ve defined specific styles for your custom block within `theme.json` (under `styles.blocks[‘your-namespace/your-block-name’]`), ensure the JSON is valid and the selectors are correct.
Leveraging `theme.json` for Theme Options and Customization
While `theme.json` is primarily for styles and settings, its structure can be extended to manage theme options that would traditionally live in the Customizer or theme options pages. By using the `custom_settings` key or by defining custom properties within `settings`, you can centralize configuration.
{
"version": 2,
"settings": {
// ... existing settings ...
"custom": {
"myThemeOptions": {
"headerLayout": "logo-left",
"footerWidgets": 3
}
}
},
"styles": {
// ... existing styles ...
}
}
You can then access these custom settings within your PHP templates or JavaScript. For PHP, you would typically retrieve them via `get_option( ‘theme_mods_’ . get_stylesheet() )` if they are saved as theme mods, or by directly parsing `theme.json` if you’re building a pure block theme. In a legacy PHP context, you might parse `theme.json` manually or use a helper function to extract these custom values.
/**
* Get a custom theme setting from theme.json.
* This is a simplified example; a robust solution would involve
* parsing theme.json more thoroughly or using WordPress core functions
* if available for custom settings.
*/
function get_my_theme_custom_setting( $key, $default = null ) {
$theme_json_data = json_decode( file_get_contents( get_template_directory() . '/theme.json' ), true );
if ( isset( $theme_json_data['settings']['custom'][$key] ) ) {
return $theme_json_data['settings']['custom'][$key];
}
// Fallback to theme mods if you've saved them there
$theme_mod_key = 'my_theme_custom_' . strtolower( str_replace( ' ', '_', $key ) );
$theme_mod_value = get_theme_mod( $theme_mod_key );
if ( $theme_mod_value !== false ) {
return $theme_mod_value;
}
return $default;
}
// Example usage in a template file:
// $header_layout = get_my_theme_custom_setting( 'headerLayout', 'logo-left' );
// if ( $header_layout === 'logo-left' ) {
// // Render logo-left header
// }
This approach allows you to consolidate theme configuration, making it easier to manage and ensuring that your theme’s behavior is consistent with its visual design defined in `theme.json`. For developers working with legacy PHP, this offers a path to adopt modern FSE principles without a complete overhaul.