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)