WordPress 6.5 stabilised the Interactivity API — a minimal reactive system built into WordPress core that lets you add client-side state, actions, and derived values to server-rendered blocks using data-wp-* directives and a store() function. It is purpose-built for progressive enhancement and works without React, without a build step for the HTML, and with full server-side rendering compatibility.
Problem: Gutenberg blocks with interactive features — a real-time search filter, a paginated carousel, a cart quantity stepper — need client-side state that updates the DOM without a full page reload, but wiring this up with vanilla JavaScript requires reinventing a reactive system.
Solution: Use the WordPress Interactivity API (stable since WordPress 6.5) — declare state with store() in a JavaScript module, bind it to DOM elements with data-wp-bind, data-wp-text, and data-wp-on--click directives, and let the Interactivity API runtime reconcile DOM updates. Server-rendered HTML is hydrated automatically.
The example below builds a filterable tag cloud block: the PHP render callback outputs the markup with data-wp-* directives, and the JavaScript store handles the filter logic — all reactive, with no full-page reload.
true, 'number' => 50 ] );
if ( empty( $tags ) ) { return; }
// Provide initial server state via wp_interactivity_state()
wp_interactivity_state( 'tag-cloud', [
'filter' => '',
'allTags' => array_map( fn($t) => [
'id' => $t->term_id,
'name' => $t->name,
'count' => $t->count,
'url' => get_tag_link( $t->term_id ),
], $tags ),
] );
?>
data-wp-interactive="tag-cloud"
>
// view.js (registered as a Script Module)
import { store, getContext, getElement } from '@wordpress/interactivity';
store( 'tag-cloud', {
actions: {
setFilter( event ) {
const { state } = store( 'tag-cloud' );
state.filter = event.target.value.toLowerCase().trim();
},
},
callbacks: {
isVisible() {
const { state } = store( 'tag-cloud' );
const { tagName } = getContext();
return state.filter === '' || tagName.includes( state.filter );
},
},
} );
NOTE: wp_interactivity_state() is merged server-side; data passed to it is inlined in the page as a JSON script tag and is publicly visible in the HTML source — never pass nonces, capability flags, or sensitive data through wp_interactivity_state(); use wp_localize_script() with a nonce for any action that requires authentication.