Unleash the power of the Interactivity API (IAPI): A New Era for WordPress

Fasten your seatbelts! ๐๏ธ
- What is the Interactivity API?
- Why might I need it and how does it work?
- When should I use it?
- Where is the roadmap headed?
What do we mean by interactivity?
Query Loop block โ seamless pagination
Image block โ Lightbox
WPMovies.dev
Pew Research Center – data tables with filtering
Pew Research Center – facet filtering for query block
What do we mean by interactivity?
- Composable blocks
- Shared state
Why do we need an Interactivity API in WordPress? ๐ค
The goals of the Interactivity API
Block-first and PHP-first
Backward compatible
Optional and gradual adoption
Declarative and reactive
Performant
Extensible
Atomic and composable
Compatible with existing block development tooling
Allow for client-side navigation โ In Progress: see #60951
IAPI Milestones
2023
March 30
–
How does it work?
Its three main components are:
- Server-side logic, handled by the HTML_Tag_Processor.
- Preact combined with Preact Signals for hydration, client logic, and client-side navigation.
- HTML Directives that can be understood by both the client and server.
Core concepts
- The Reactive and Declarative mindset
- Understanding global state, local context and derived state
- Server-side rendering: Processing directives on the server
Reactive + Declarative = IAPI
Declarative vs. Imperative
- Imperative โ how to accomplish tasks by explicitly stating each step to manipulate the programโs state.
- Declarative โ what a program should accomplish.
The Imperative approach
HTML
๐ด ๐ก ๐ข
some-plugin.html
<div id="my-interactive-plugin">
<button
id="show-hide-btn"
aria-expanded="false"
aria-controls="status-paragraph"
>
show
</button>
<button id="activate-btn" disabled>activate</button>
<p id="status-paragraph" class="inactive" hidden>this is inactive</p>
</div>
JavaScript
๐ด ๐ก ๐ข
some-plugin.
js
const showHideBtn = document.getElementById( 'show-hide-btn' );
const activateBtn = document.getElementById( 'activate-btn' );
const statusParagraph = document.getElementById( 'status-paragraph' );
showHideBtn.addEventListener( 'click', () => {
if ( statusParagraph.hasAttribute( 'hidden' ) ) {
statusParagraph.removeAttribute( 'hidden' );
showHideBtn.textContent = 'hide';
showHideBtn.setAttribute( 'aria-expanded', 'true' );
activateBtn.removeAttribute( 'disabled' );
} else {
if ( statusParagraph.classList.contains( 'active' ) ) {
statusParagraph.textContent = 'this is inactive';
statusParagraph.classList.remove( 'active' );
activateBtn.textContent = 'activate';
}
statusParagraph.setAttribute( 'hidden', true );
showHideBtn.textContent = 'show';
showHideBtn.setAttribute( 'aria-expanded', 'false' );
activateBtn.setAttribute( 'disabled', true );
}
} );
activateBtn.addEventListener( 'click', () => {
if ( activateBtn.textContent === 'activate' ) {
statusParagraph.textContent = 'this is active';
statusParagraph.classList.remove( 'inactive' );
statusParagraph.classList.add( 'active' );
activateBtn.textContent = 'deactivate';
} else {
statusParagraph.textContent = 'this is inactive';
statusParagraph.classList.remove( 'active' );
statusParagraph.classList.add( 'inactive' );
activateBtn.textContent = 'activate';
}
} );
The Declarative approach
HTML
๐ด ๐ก ๐ข
render.php
<div id="my-interactive-plugin" data-wp-interactive="myInteractivePlugin">
<button
data-wp-on--click="actions.toggleVisibility"
data-wp-bind--aria-expanded="state.isVisible"
data-wp-text="state.visibilityText"
aria-controls="status-paragraph"
>
show
</button>
<button
data-wp-on--click="actions.toggleActivation"
data-wp-bind--disabled="!state.isVisible"
data-wp-text="state.activationText"
>
activate
</button>
<p
id="status-paragraph"
data-wp-bind--hidden="!state.isVisible"
data-wp-class--active="state.isActive"
data-wp-class--inactive="!state.isActive"
data-wp-text="state.paragraphText"
>
this is inactive
</p>
</div>
JavaScript
๐ด ๐ก ๐ข
view.
js
import { store } from '@wordpress/interactivity';
const { state } = store( 'myInteractivePlugin', {
state: {
isVisible: false,
isActive: false,
get visibilityText() {
return state.isVisible ? 'hide' : 'show';
},
get activationText() {
return state.isActive ? 'deactivate' : 'activate';
},
get paragraphText() {
return state.isActive ? 'this is active' : 'this is inactive';
},
},
actions: {
toggleVisibility() {
state.isVisible = ! state.isVisible;
if ( ! state.isVisible ) state.isActive = false;
},
toggleActivation() {
state.isActive = ! state.isActive;
},
},
} );
Reactivity
IAPI uses a fine-grained reactivity system:
- Reactive state: Mutates based on global and local state and updates the UI. There are three types of state: global, local, and derived.
- Actions: These are functions, usually triggered by event handlers, that mutate the global state or local context.
- Reactive Bindings: HTML elements are bound to reactive state values using special attributes like
data-wp-bind
,data-wp-text
, ordata-wp-class
. - Automatic Updates: When the actions mutate the global state or local context, the Interactivity API automatically updates all the parts of the DOM that depend on that state (either directly or through the derived state).
Types of reactive state
- Global state โ can be shared between blocks
- Local context โ specific to the targeted element and its children
- Derived state โ computed properties that update when their dependencies change
Breaking it down
๐ด ๐ก ๐ข
view.js
const { state } = store( 'myInteractivePlugin', {
state: {
isVisible: false, // state properties
isActive: false,
// Derived state, automatically updates
get visibilityText() {
return state.isVisible ? 'hide' : 'show';
},
// ... other derived state
},
actions: {
// Modifies the state
toggleVisibility() {
state.isVisible = ! state.isVisible;
},
// ... other actions
},
} );
Server Directive Processing
Global state
๐ด ๐ก ๐ข
render.php
<?php
// Defining global state.
wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( 'Apple', 'Banana', 'Cherry' )
) );
?>
<ul data-wp-interactive="myFruitPlugin">
<template data-wp-each="state.fruits">
<li data-wp-text="context.item"></li>
</template>
</ul>
Local context (inline)
๐ด ๐ก ๐ข
render.php
<ul data-wp-context='{ "fruits": ["Apple", "Banana", "Cherry"] }'>
...
</ul>
Local context (wp_interactivity_data_wp_context)
๐ด ๐ก ๐ข
render.php
<?php
$context = array( 'fruits' => array( 'Apple', 'Banana', 'Cherry' ) );
?>
<ul <?php echo wp_interactivity_data_wp_context( $context ); ?>>
...
</ul>
Resulting HTML markup
๐ด ๐ก ๐ข
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
Directives
Custom attributes are added to your block’s markup to add behavior to its DOM elements. IAPI directives use the data-wpโprefix
.
๐ด ๐ก ๐ข
render.php
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "isOpen": false }'
data-wp-watch="callbacks.logIsOpen"
>
<button
data-wp-on--click="actions.toggle"
data-wp-bind--aria-expanded="context.isOpen"
aria-controls="p-1"
>
Toggle
</button>
<p id="p-1" data-wp-bind--hidden="!context.isOpen">
This element is now visible!
</p>
</div>
wp-interactive
directive
โActivatesโ the interactivity for the DOM element and its children.
Simple string example
๐ด ๐ก ๐ข
render.php
<!-- Let's make this element and its children interactive and set the namespace -->
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
Namespace object example
๐ด ๐ก ๐ข
render.php
<!-- Let's make this element and its children interactive and set the namespace -->
<div
data-wp-interactive='{ "namespace": "myPlugin" }'
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
wp-context
Provides a local state available to a specific HTML node and its children.
Context directive in render.php
๐ด ๐ก ๐ข
render.php
<div data-wp-context='{ "post": { "id": <?php echo $post->ID; ?> } }'>
<button data-wp-on--click="actions.logId">
Click Me!
</button>
</div>
Referencing the store
๐ด ๐ก ๐ข
view.js
store( "myPlugin", {
actions: {
logId: () => {
const { post } = getContext();
console.log( post.id );
},
},
} );
Nesting wp-context
Different contexts can be defined at different levels, and deeper levels will merge their context with any parent.
๐ด ๐ก ๐ข
render.php
<div data-wp-context="{ foo: 'bar' }">
<span data-wp-text="context.foo"><!-- Will output: "bar" --></span>
<div data-wp-context="{ bar: 'baz' }">
<span data-wp-text="context.foo"><!-- Will output: "bar" --></span>
<div data-wp-context="{ foo: 'bob' }">
<span data-wp-text="context.foo"><!-- Will output: "bob" --></span>
</div>
</div>
</div>
The store
Used to create the logic (actions, side effects, etc.) linked to the directives and the data used inside that logic (state, derived state, etc.).
The store is usually created in the view.js
file of each block, although the state can be initialized from the render.php
of the block.
Where do I start?
Block Editor Handbook / Reference Guides / Interactivity API Reference / Quick start guide
Requirements
Create a new block with IAPI
@wordpress/create-block
Interactivity API template for scaffolding
๐ด ๐ก ๐ข
npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template
๐ด ๐ก ๐ข
cd my-first-interactive-block && npm start
Block Editor Handbook -> Interactivity API -> Quick Start Guide
Integrate IAPI into an existing block
Add interactivity
support in block.json
๐ด ๐ก ๐ข
block.json
"supports": {
"interactivity": true
},
"viewScriptModule": "file:./view.js"
Add wp-interactive
directive to a DOM element
๐ด ๐ก ๐ข
render.php
<div data-wp-interactive="myPlugin">
<!-- Interactivity API zone -->
</div>
Block Editor Handbook -> Interactivity API
Dive deeper and keep learning
Existing examples
Gallery Slider – Ryan Welcher
To-do List – Ryan Welcher
WordPress core blocks: Image lightbox, Search show/hide, Query Loop enhanced pagination
Pew Research Center’s PRC Block Library

IAPI Contributors

Thanks! ๐ซถ
Key pieces of IAPI
- Directives (HTML attributes:
data-wp-xyz
) - Store – logic and data manipulation (state, actions, side-effects)