• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Building a Reactive Frontend Framework inside Full Site Editing (FSE) Block Themes and theme.json Without Breaking Site Responsiveness

Building a Reactive Frontend Framework inside Full Site Editing (FSE) Block Themes and theme.json Without Breaking Site Responsiveness

Leveraging `theme.json` for Reactive Frontend Logic in FSE Block Themes

Full Site Editing (FSE) and block themes have fundamentally shifted WordPress development towards a declarative, component-based approach. However, achieving truly reactive frontend experiences—where UI elements dynamically update based on state changes or user interactions—within this paradigm, especially when relying heavily on theme.json for styling and layout, presents unique challenges. This post dives into advanced techniques for building reactive frontend components that integrate seamlessly with FSE block themes and theme.json, ensuring site responsiveness and maintainability.

Understanding the `theme.json` Constraint and Opportunity

theme.json acts as the central configuration file for block themes, defining global styles, color palettes, typography settings, spacing, and layout constraints. While it excels at declarative styling, it’s not inherently designed for imperative frontend logic or state management. Our goal is to augment this declarative system with reactive capabilities without resorting to monolithic JavaScript bundles that bypass the FSE architecture.

The key is to leverage theme.json‘s structure to inform and control frontend JavaScript, rather than trying to embed complex logic directly within it. This involves strategically using theme.json‘s settings to pass data to JavaScript, and using JavaScript to dynamically modify DOM elements or trigger re-renders based on state.

Injecting Dynamic Data via `theme.json` Settings

One of the most effective ways to bridge the gap between theme.json and frontend logic is by defining custom settings within theme.json that can be accessed by JavaScript. These custom settings can represent dynamic data, feature flags, or configuration parameters that influence the behavior of your reactive components.

Consider a scenario where you want to conditionally display a “New Arrivals” badge on product blocks based on a site-wide setting. This setting can be managed within theme.json.

Example: Defining a Custom Setting in theme.json

Add a custom section to your theme.json file:

{
    "version": 2,
    "settings": {
        "custom": {
            "features": {
                "showNewArrivalsBadge": true,
                "newArrivalsThresholdDays": 7
            },
            "apiEndpoints": {
                "products": "/wp-json/myplugin/v1/products"
            }
        }
    },
    // ... other theme.json settings
}

Accessing `theme.json` Settings in JavaScript

WordPress enqueues theme settings into the global JavaScript scope. You can access these values using the wp.data store, specifically the core/editor or core data stores, depending on the context (editor vs. frontend). For frontend access, these settings are often made available via a localized script handle.

First, ensure your JavaScript file is enqueued and localized with the theme settings. This is typically done in your theme’s functions.php or a dedicated plugin.

// In your theme's functions.php or a plugin file
function my_theme_enqueue_scripts() {
    wp_enqueue_script(
        'my-theme-reactivity',
        get_template_directory_uri() . '/assets/js/reactivity.js',
        array( 'wp-element', 'wp-data' ), // Dependencies for React and WP data API
        filemtime( get_template_directory() . '/assets/js/reactivity.js' ),
        true
    );

    // Localize theme settings
    wp_localize_script(
        'my-theme-reactivity',
        'myThemeSettings',
        array(
            'showNewArrivalsBadge' => get_theme_mod( 'show_new_arrivals_badge', true ), // Example using theme mods, but ideally read from theme.json
            'newArrivalsThresholdDays' => get_theme_mod( 'new_arrivals_threshold_days', 7 ),
            'apiEndpoints' => array(
                'products' => get_option( 'myplugin_products_api_endpoint', '/wp-json/myplugin/v1/products' ) // Example reading from options
            )
        )
    );

    // For direct theme.json access in the frontend, you might need a more direct approach
    // or ensure it's passed via wp_add_inline_script if not automatically available.
    // A common pattern is to expose it via a global JS variable.
    $theme_json = json_decode( file_get_contents( get_template_directory() . '/theme.json' ), true );
    if ( isset( $theme_json['settings']['custom'] ) ) {
        wp_add_inline_script(
            'my-theme-reactivity',
            'const themeCustomSettings = ' . json_encode( $theme_json['settings']['custom'] ) . ';',
            'before'
        );
    }
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_scripts' );

Now, in your reactivity.js file, you can access these settings:

// assets/js/reactivity.js
document.addEventListener( 'DOMContentLoaded', () => {
    // Accessing settings localized via wp_localize_script (if not using themeCustomSettings)
    // const showBadge = myThemeSettings.showNewArrivalsBadge;
    // const threshold = myThemeSettings.newArrivalsThresholdDays;

    // Accessing settings directly from theme.json via wp_add_inline_script
    const showBadge = themeCustomSettings.features.showNewArrivalsBadge;
    const threshold = themeCustomSettings.features.newArrivalsThresholdDays;
    const productsApiEndpoint = themeCustomSettings.apiEndpoints.products;

    if ( ! showBadge ) {
        console.log( 'New arrivals badge feature is disabled.' );
        return;
    }

    // Logic to fetch products and display badges
    fetchProductsAndDisplayBadges( productsApiEndpoint, threshold );
} );

function fetchProductsAndDisplayBadges( apiUrl, thresholdDays ) {
    fetch( apiUrl )
        .then( response => response.json() )
        .then( products => {
            products.forEach( product => {
                const productDate = new Date( product.date_created ); // Assuming 'date_created' field
                const now = new Date();
                const timeDiff = now.getTime() - productDate.getTime();
                const daysDiff = Math.ceil( timeDiff / ( 1000 * 3600 * 24 ) );

                if ( daysDiff <= thresholdDays ) {
                    const productElement = document.querySelector( `.product-id-${product.id}` ); // Target specific product element
                    if ( productElement ) {
                        const badge = document.createElement( 'span' );
                        badge.className = 'new-arrivals-badge';
                        badge.textContent = 'New!';
                        productElement.prepend( badge ); // Or append, depending on desired placement
                    }
                }
            } );
        } )
        .catch( error => console.error( 'Error fetching products:', error ) );
}

// Example of a reactive component using React (if you're using it in your theme)
// This would typically be part of a larger React application bundled for the theme.
/*
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

function ProductList() {
    const [products, setProducts] = useState([]);
    const showBadge = themeCustomSettings.features.showNewArrivalsBadge;
    const threshold = themeCustomSettings.features.newArrivalsThresholdDays;
    const productsApiEndpoint = themeCustomSettings.apiEndpoints.products;

    useEffect(() => {
        fetch(productsApiEndpoint)
            .then(res => res.json())
            .then(data => setProducts(data))
            .catch(err => console.error("Error fetching products:", err));
    }, []);

    return (
        
{products.map(product => { const productDate = new Date(product.date_created); const now = new Date(); const timeDiff = now.getTime() - productDate.getTime(); const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); const isNew = showBadge && daysDiff <= threshold; return (

{product.name}

{isNew && New!}

{product.price}

); })}
); } // Assuming you have a div with id="product-list-container" in your template // const container = document.getElementById('product-list-container'); // if (container) { // ReactDOM.render(, container); // } */

Dynamic Styling Based on `theme.json` and State

theme.json defines global styles, but reactive components often require dynamic styling that changes based on component state or user interaction. We can achieve this by:

  • Using JavaScript to add/remove CSS classes to elements.
  • Dynamically setting inline styles via JavaScript.
  • Leveraging CSS Custom Properties (variables) defined in theme.json and updated by JavaScript.

Example: Dynamic Badges with Themed Styles

Let’s enhance the “New Arrivals” badge. We want its color to be customizable via theme.json.

1. Define Color Variables in theme.json

{
    "version": 2,
    "settings": {
        "color": {
            "custom": {},
            "palette": [
                // ... existing palette
            ]
        },
        "custom": {
            "features": {
                "showNewArrivalsBadge": true,
                "newArrivalsThresholdDays": 7
            },
            "apiEndpoints": {
                "products": "/wp-json/myplugin/v1/products"
            },
            "badgeStyles": {
                "newArrivalsColor": "var(--wp--preset--color--primary)", // Uses a theme preset
                "newArrivalsBackgroundColor": "var(--wp--preset--color--secondary)"
            }
        }
    },
    // ...
}

Here, we’re referencing existing color presets. You could also define direct hex values.

2. Enqueue CSS for the Badge

Create a CSS file (e.g., assets/css/reactivity.css) and enqueue it.

/* assets/css/reactivity.css */
.new-arrivals-badge {
    display: inline-block;
    padding: 0.3em 0.6em;
    font-size: 0.75em;
    font-weight: bold;
    line-height: 1;
    text-align: center;
    white-space: nowrap;
    vertical-align: baseline;
    border-radius: 0.25rem;
    margin-right: 0.5rem; /* Adjust spacing */
    /* Dynamic styles will be applied via inline styles or CSS variables */
}

/* If using CSS variables directly */
.new-arrivals-badge {
    color: var(--badge-text-color);
    background-color: var(--badge-bg-color);
}
// In functions.php or plugin
function my_theme_enqueue_styles() {
    // ... enqueue scripts ...

    wp_enqueue_style(
        'my-theme-reactivity-styles',
        get_template_directory_uri() . '/assets/css/reactivity.css',
        array(), // Dependencies
        filemtime( get_template_directory() . '/assets/css/reactivity.css' )
    );
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' );

3. Apply Dynamic Styles with JavaScript

Modify the JavaScript to apply the styles. We can either set inline styles or, preferably, set CSS Custom Properties on the element.

// assets/js/reactivity.js (updated)
document.addEventListener( 'DOMContentLoaded', () => {
    const showBadge = themeCustomSettings.features.showNewArrivalsBadge;
    const threshold = themeCustomSettings.features.newArrivalsThresholdDays;
    const productsApiEndpoint = themeCustomSettings.apiEndpoints.products;

    if ( ! showBadge ) {
        return;
    }

    // Get badge style settings from theme.json
    const badgeStyles = themeCustomSettings.badgeStyles || {};
    const textColor = badgeStyles.newArrivalsColor || 'var(--wp--preset--color--white)'; // Default to white text
    const bgColor = badgeStyles.newArrivalsBackgroundColor || 'var(--wp--preset--color--primary)'; // Default to primary color

    fetch( productsApiEndpoint )
        .then( response => response.json() )
        .then( products => {
            products.forEach( product => {
                const productDate = new Date( product.date_created );
                const now = new Date();
                const timeDiff = now.getTime() - productDate.getTime();
                const daysDiff = Math.ceil( timeDiff / ( 1000 * 3600 * 24 ) );

                if ( daysDiff <= threshold ) {
                    const productElement = document.querySelector( `.product-id-${product.id}` );
                    if ( productElement ) {
                        const badge = document.createElement( 'span' );
                        badge.className = 'new-arrivals-badge';
                        badge.textContent = 'New!';

                        // Option 1: Set CSS Custom Properties (Recommended)
                        badge.style.setProperty( '--badge-text-color', textColor );
                        badge.style.setProperty( '--badge-bg-color', bgColor );

                        // Option 2: Set inline styles directly (less flexible)
                        // badge.style.color = textColor;
                        // badge.style.backgroundColor = bgColor;

                        productElement.prepend( badge );
                    }
                }
            } );
        } )
        .catch( error => console.error( 'Error fetching products:', error ) );
} );

This approach allows users to customize the badge’s appearance through the WordPress Customizer or Site Editor, by modifying the corresponding color presets or by defining custom colors that are then referenced in theme.json. The JavaScript dynamically applies these settings.

Building Reactive Components with Block Filters and JavaScript

For more complex interactions or state management within the FSE context, you can leverage WordPress’s block filters. These filters allow you to modify block behavior, attributes, and even render output using JavaScript, effectively creating dynamic or reactive components.

Example: A “Read More” Toggle for Post Excerpts

Imagine you want to add a “Read More” button to post excerpts that expands the excerpt on click. This can be achieved by filtering the core core/post-excerpt block.

1. Registering a Block Filter

// assets/js/block-filters.js
wp.hooks.addFilter(
    'blocks.getSaveElement', // Filter for the saved (rendered) output
    'my-theme/excerpt-read-more',
    function( element, blockType, attributes ) {
        // Only apply to the core/post-excerpt block
        if ( blockType.name === 'core/post-excerpt' ) {
            // Check if the excerpt is short enough to warrant a "read more"
            // This logic might need to be more sophisticated, perhaps checking content length
            // or a custom attribute. For simplicity, let's assume we always add it.

            // We need to ensure the excerpt content is available and potentially wrap it.
            // The 'element' here is the React element representing the saved output.
            // We'll need to modify its children or add a sibling.

            // This is a simplified example. Real-world implementation might involve
            // checking attributes, content length, and ensuring proper DOM structure.
            // A more robust solution might involve a custom block or modifying the
            // block's edit/save components directly if you're building custom blocks.

            // For a simple toggle, we might add a button and use CSS/JS to control visibility.
            // Let's assume the excerpt element has a specific class or structure.

            // A common pattern is to add a wrapper div and control its height/overflow.
            if ( element && element.props && element.props.children ) {
                const excerptContent = element.props.children;
                const wrapper = wp.element.createElement(
                    'div',
                    {
                        className: 'excerpt-wrapper',
                        style: { maxHeight: '100px', overflow: 'hidden', transition: 'max-height 0.3s ease-in-out' }
                    },
                    excerptContent
                );

                const readMoreButton = wp.element.createElement(
                    'button',
                    {
                        className: 'read-more-button',
                        onClick: () => {
                            const wrapperElement = document.querySelector('.excerpt-wrapper'); // Find the specific wrapper
                            if (wrapperElement) {
                                wrapperElement.style.maxHeight = wrapperElement.scrollHeight + 'px';
                                // Optionally hide the button after expanding
                                // document.querySelector('.read-more-button').style.display = 'none';
                            }
                        }
                    },
                    'Read More'
                );

                // Return a new element structure
                return wp.element.createElement(
                    'div',
                    { className: 'post-excerpt-container' },
                    wrapper,
                    readMoreButton
                );
            }
        }
        return element; // Return original element if not the target block
    }
);

// Add filter for the editor view as well, to ensure consistency
wp.hooks.addFilter(
    'editor.BlockEdit',
    'my-theme/excerpt-read-more-edit',
    function( BlockEdit ) {
        return function( props ) {
            if ( props.name === 'core/post-excerpt' ) {
                // Logic to add the button in the editor might be different,
                // potentially requiring modification of the block's edit component.
                // This is often more complex than modifying the save output.
                // For simplicity, we'll focus on the frontend rendering here.
            }
            return wp.element.createElement( BlockEdit, props );
        };
    }
);

2. Enqueue the JavaScript and CSS

Ensure block-filters.js is enqueued, along with corresponding CSS for .excerpt-wrapper and .read-more-button.

// In functions.php or plugin
function my_theme_enqueue_block_filters() {
    wp_enqueue_script(
        'my-theme-block-filters',
        get_template_directory_uri() . '/assets/js/block-filters.js',
        array( 'wp-hooks', 'wp-element', 'wp-editor' ), // Dependencies
        filemtime( get_template_directory() . '/assets/js/block-filters.js' ),
        true
    );
    // Enqueue corresponding CSS for styling the wrapper and button
}
add_action( 'enqueue_block_editor_assets', 'my_theme_enqueue_block_filters' ); // For editor
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_block_filters' ); // For frontend

This filter approach allows you to inject reactive behavior into existing blocks without creating entirely new custom blocks, promoting code reuse and adherence to the FSE structure. The key is understanding how block filters interact with the block’s save/edit functions and the underlying React components.

Advanced Diagnostics and Troubleshooting

When building reactive features within FSE themes, several issues can arise. Here’s a diagnostic checklist:

1. JavaScript Errors in the Console

Symptom: Features not working, unexpected behavior, errors in the browser’s developer console.

  • Check Enqueuing: Verify that your JavaScript files are correctly enqueued using wp_enqueue_script and that dependencies (like wp-element, wp-hooks, wp-data) are listed. Use browser developer tools (Network tab) to confirm the script is loaded.
  • Check Localization: Ensure any localized data (like theme settings) is correctly passed and accessible via the expected global variable (e.g., myThemeSettings, themeCustomSettings). Use console.log() liberally to inspect variables.
  • Scope Issues: Be mindful of JavaScript scope. Variables defined within one function might not be accessible in another. Ensure your event listeners (like DOMContentLoaded) are correctly placed.
  • React/WP Element Errors: If using React components or wp.element, ensure you’re using the correct API calls and that the necessary WordPress packages are loaded.

2. `theme.json` Settings Not Reflecting

Symptom: Features controlled by theme.json settings (like the badge visibility) are not behaving as expected.

  • Cache Clearing: WordPress and browser caches can sometimes serve stale data. Clear all caches (WordPress, CDN, browser).
  • Correct Path to theme.json: Ensure the PHP code reading theme.json is using the correct path (e.g., get_template_directory() . '/theme.json').
  • JSON Validity: Validate your theme.json file using an online JSON validator. Syntax errors can prevent it from being parsed correctly.
  • Access Method: Double-check how you’re accessing the settings in JavaScript. If using wp_add_inline_script, ensure the variable name matches. If relying on automatic global exposure, confirm that mechanism is active and correct for your WordPress version.
  • Theme Mod vs. Direct Read: If you’re mixing get_theme_mod() with direct theme.json reads, ensure consistency. For FSE, relying on direct reads or specific data stores is generally preferred over theme mods for global settings.

3. Responsiveness Issues

Symptom: Layout breaks on different screen sizes, elements overlap, or become unusable.

  • CSS Specificity and Cascade: Ensure your custom CSS for reactive elements has sufficient specificity or is placed correctly in the cascade to override default theme styles. Use browser developer tools to inspect applied styles.
  • Mobile-First Design: Apply responsive styles using media queries in your CSS. Ensure that dynamic elements (like badges or toggles) are designed to fit within smaller viewports.
  • Viewport Units: Be cautious with viewport units (vw, vh) if your reactive elements significantly alter layout. They can sometimes lead to unexpected results.
  • JavaScript-Driven Layout: If JavaScript is directly manipulating element dimensions or positions, ensure these calculations are robust and account for different screen sizes and content variations. Test thoroughly across devices or using browser emulation.
  • `theme.json` Layout Settings: Ensure your JavaScript logic doesn’t conflict with layout constraints defined in theme.json (e.g., contentSize, wideSize).

Conclusion

Building reactive frontend experiences within FSE block themes requires a thoughtful integration of declarative styling from theme.json and imperative JavaScript logic. By strategically using theme.json to pass configuration, leveraging block filters for dynamic behavior, and employing robust CSS and JavaScript practices, developers can create sophisticated, responsive, and maintainable user interfaces. The key is to view theme.json as a powerful configuration layer that informs, rather than dictates, frontend interactivity, ensuring that the core principles of FSE are maintained while delivering modern web application experiences.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala