The WordPress Interactivity API (stable since WordPress 6.5) provides a directive-based reactivity system for blocks — similar to Alpine.js but fully integrated with Server-Side Rendering and the block editor. It replaces ad-hoc jQuery for block interactivity without requiring React on the front end.
Problem: Gutenberg blocks with interactive features — accordions, tabs, live filters — traditionally require writing separate vanilla JavaScript or a React-based approach that duplicates server-rendered state on the client.
Solution: Use the WordPress Interactivity API (stable since WordPress 6.5) — declare reactive state in a wp-interactive wrapper, bind it to DOM elements with data-wp-bind, data-wp-on, and data-wp-context directives, and handle actions with data-wp-on--click. No React or custom framework is needed.
The examples below build an interactive counter block using wp-context, wp-bind, and wp-on directives, and show how to share state between multiple blocks on the same page.
0, 'step' => 1 ] );
// wp_interactivity_data_wp_context() serialises context for the directive
$context = wp_interactivity_data_wp_context( [ 'count' => 0 ] );
?>
data-wp-interactive="myCounter"
>
The JavaScript store — registered once, works for every counter block instance on the page:
// view.js — enqueued as viewScriptModule in block.json
import { store, getContext, getElement } from '@wordpress/interactivity';
const { state, actions } = store( 'myCounter', {
state: {
// Derived state: computed from context
get isAtMin() {
return getContext().count <= 0;
},
},
actions: {
increment() {
const context = getContext();
context.count += state.step; // state.step from wp_interactivity_state()
},
decrement() {
const context = getContext();
if ( context.count > 0 ) context.count -= state.step;
},
},
callbacks: {
// Runs whenever context.count changes — like a watcher
logChange() {
const { count } = getContext();
console.log( 'Count changed to:', count );
},
},
} );
// Share state between blocks: import the same store namespace in both blocks
// const { state } = store( 'myCounter' ); — read-only access from another block
NOTE: The Interactivity API requires blocks to use apiVersion: 3 and declare "interactivity": true in block.json. Scripts are loaded as ES modules (viewScriptModule) so you get native import/export without a bundler for simple blocks.