WordPress.org

WordPress Developer Blog

A first look at the Interactivity API

A first look at the Interactivity API

The Interactivity API was included in WordPress 6.4 as a private API that could only be used by Core blocks. With the release of WordPress 6.5, the API has been made public and is now available for developers to use in their custom projects.

Until recently, Gutenberg development primarily focused on enhancing the backend of blocks, which mainly affected content creators. The Interactivity API, on the other hand, focuses on the frontend.

Typically, users interact with the website by clicking or scrolling through the page. The Interactivity API allows you to add behaviors to blocks that will happen in response to user interactions. These may include simple effects, such as showing or hiding elements on click or animating elements on scroll, as well as more advanced features like navigation without page reloads or instant search.

A practical example of a feature built with the Interactivity API is the lightbox option in the Core Image block, added in WordPress 6.4.

Traditionally, features like this one have a long history of being coded with jQuery, and even today, many themes and plugins continue to rely on it. The Interactivity API aims to decrease jQuery usage in WordPress projects and become the new standard for coding interactive elements.

General overview of the Interactivity API

The Interactivity API shares many similarities with other JS frameworks, especially Alpine.js, which makes it accessible for anyone already familiar with the modern JavaScript ecosystem.

It extends standard HTML with directives, which are special data attributes that can listen to and modify the behavior of any DOM element. With directives, you can manipulate the DOM, apply CSS styles, handle user input, and much more.

It’s important to highlight that the block creation workflow doesn’t change. To add interactivity to a block you need to extend it with new features of the Interactivity API. You still can use the @wordpress/create-block scaffolding tool and develop the editor part of the block just as you normally do.

To add interactivity to blocks using the Interactivity API, you need to:

  • Add directives as data attributes to the HTML markup to add specific behaviors to the block. You would usually add them in the render.php file within the block directory (only dynamic blocks are supported in WordPress 6.5). The list of all publicly available directives can be found in the Interactivity API Reference
  • Create a store with the logic (state, actions, or callbacks) needed for interactivity. Typically, it is added in the view.js file within the block directory. In the store, actions can be defined as functions that run in response to user interaction, such as a click. Actions update the global state or the local context, which, in turn, updates the HTML element connected to either of them. Besides actions, you can also define callbacks that would run as side effects. Callbacks are also functions, but are not directly invoked by user interactions, they are invoked by the state change. You can read more about the store in the documentation

The following section of this article will demonstrate how to code a block for a charity that allows users to calculate how many trees will be planted for the donation they plan to make. By the end, you should have a good grasp of how to use the Interactivity API.

This is what you’re going to build:

Prerequisites

This tutorial expects that you are familiar with the fundamentals of building custom blocks, such as development environment, file structure, block.json metadata, static vs. dynamic rendering of a block, etc. It also assumes that you’ve already built at least a basic block. If this is not the case, I would recommend reading the Build your first block tutorial first.

Creating a basic toggle block

The first step is to scaffold the initial block structure using the Interactivity API template @wordpress/create-block-interactive-template. It’s best to run it from the /plugins directory in a local WordPress installation.

npx @wordpress/create-block@latest donation-calculator --template @wordpress/create-block-interactive-template

Next, use the command cd donation-calculator to navigate to the block directory and start the development with npm start.

The scaffolding tool has created a basic interactive toggle block, which should be available for use in the editor after the plugin is activated in the WordPress admin panel.

Let’s now inspect the generated code to understand what makes this block interactive.

Open the block.json file in the/src folder. In the supports section, you should see that the interactivity is set to true. This line is necessary to enable the Interactivity API in the block. Also, ensure that the viewScriptModule property is defined, and the path to the view.js file is correct.

"supports": {
	"interactivity": true
},
"viewScriptModule": "file:./view.js"

wp-interactive directive

Now open the render.php file with the frontend markup of the block.

<?php
$unique_id = wp_unique_id( 'p-' );
?>

<div
	<?php echo get_block_wrapper_attributes(); ?>
	data-wp-interactive="create-block"
	<?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ); ?>
	data-wp-watch="callbacks.logIsOpen"
>
	<button
		data-wp-on--click="actions.toggle"
		data-wp-bind--aria-expanded="context.isOpen"
		aria-controls="<?php echo esc_attr( $unique_id ); ?>"
	>
		<?php esc_html_e( 'Toggle', 'donation-calculator' ); ?>
	</button>

	<p
		id="<?php echo esc_attr( $unique_id ); ?>"
		data-wp-bind--hidden="!context.isOpen"
	>
		<?php
			esc_html_e( 'Donation Calculator - hello from an interactive block!', 'donation-calculator' );
		?>
	</p>
</div>

A few data attributes have been added to the HTML markup. These are the directives that were mentioned earlier in the article. They all follow data-wp-directiveName syntax.

The wp-interactive directive activates interactivity for the wrapping <div> and its children through the Interactivity API. It includes a namespace create-block to reference the store defined in the view.js file.

import { store, getContext } from '@wordpress/interactivity';

store( 'create-block', {
	actions: {
		toggle: () => {
			const context = getContext();
			context.isOpen = ! context.isOpen;
		},
	},
	callbacks: {
		logIsOpen: () => {
			const { isOpen } = getContext();
			// Log the value of `isOpen` each time it changes.
			console.log( `Is open: ${ isOpen }` );
		},
	},
} );

The namespace in the wp-interactive directive should match the first argument of the store function. You can change the create-block namespace to the one that matches the plugin name – the donation-calculator in this example. This change needs to be updated in both files – render.php and view.js.

wp-context directive

The interaction in this block is quite common – it shows or hides the paragraph when the user clicks on the button. To store information about the paragraph’s visibility, you need a context. With the Interactivity API, you can use wp_interactivity_data_wp_context() function for this, which can be added to the wrapping <div>. The context is local and is available to <div> and all its children, but not to other blocks.

The wp_interactivity_data_wp_context() function returns a stringified JSON of a context directive. In this block, wp-context stores information about the visibility of the paragraph under the isOpen key, with the initial value of false. This value will later be used to conditionally add or remove the hidden attribute from the paragraph in response to user interaction.

<div
	<?php echo get_block_wrapper_attributes(); ?>
	data-wp-interactive="donation-calculator"
	<?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ); ?>
	data-wp-watch="callbacks.logIsOpen"
>

wp-on directive

Next, an event listener needs to be added to the button to handle the click. The Interactivity API provides a wp-on directive to listen to DOM events.

The directive follows the syntax data-wp-on--[event].

In this example, the data-wp-on--click directive is added to the button. The name of the function, toggle, declared in the store as an action, is passed as a value.

<button
	data-wp-on--click="actions.toggle"
	data-wp-bind--aria-expanded="context.isOpen"
	aria-controls="<?php echo esc_attr( $unique_id ); ?>"
>
	<?php esc_html_e( 'Toggle', 'donation-calculator' ); ?>
</button>

Next, the logic needs to be created – what should happen when the user clicks the button. Now, because the Interactivity API follows a declarative approach, the event triggered by user interaction doesn’t directly modify the HTML element. Instead, it should update the state or the context, and the Interactivity API will take care of updating the element connected to either of them.

In this block, when the user clicks on the button, it triggers an action that sets the isOpen context to the opposite value (eg., from false to true). The Interactivity API then automagically shows or hides the <p> element.

Let’s open the view.js file and inspect the toggle() function defined in the actions property of the store.

store( 'donation-calculator', {
	actions: {
		toggle: () => {
			const context = getContext();
			context.isOpen = ! context.isOpen;
		},
	},
});

The toggle() function first retrieves the current context properties with getContext() and then sets the isOpen property to the opposite value. That’s all the function does.

To verify if the logic works as expected, you can add console.log( context.isOpen ); under the last line of the toggle() function. Now, when you click on the button, you should see in the console that the value changes with every click.

The Interactivity API also provides directives for window and document event listeners. For further details, you can refer to the documentation.

wp-bind directive

To make the toggle block work, you need to connect the paragraph that you want to show or hide to the isOpen context. You use the wp-bind directive, which allows setting HTML attributes on elements based on a boolean or string value.

The directive follows the syntax data-wp-bind--[attribute].

In this example, a hidden attribute should be added to the paragraph only when the isOpen context is set to false. So the data-wp-bind--hidden attribute needs to be added to the <p> element with the opposite value of context.isOpen assigned to it.

<p
	id="<?php echo esc_attr( $unique_id ); ?>"
	data-wp-bind--hidden="!context.isOpen"
>
	<?php
	esc_html_e( 'Donation Calculator - hello from an interactive block!', 'donation-calculator' );
	?>
</p>

When isOpen is set to false, the hidden attribute will be added to <p>, so it will no longer be visible. When it is set to true, the hidden attribute will be removed and the paragraph will be visible again.

wp-class directive

Alternatively, you could hide the paragraph by adding a class .hidden, which would have a CSS property display set to none. The Interactivity API includes a wp-class directive that adds or removes a class conditionally based on a boolean value.

The directive follows the syntax data-wp-class--[classname].

Let’s go ahead and replace the data-wp-bind--hidden attribute on the paragraph with data-wp-class--hidden, and leave the value as it is. You also need to add the .hidden {display: none} code to the style.scss file in the block directory.

<p
	id="<?php echo esc_attr( $unique_id ); ?>"
	data-wp-class--hidden="!context.isOpen"
>
	<?php
	esc_html_e( 'Donation Calculator - hello from an interactive block!', 'donation-calculator' );
	?>
</p>

When isOpen is set to false, the class="hidden" attribute will be added to <p>, making it no longer visible. When it is set to true, the class will be removed.

wp-style directive

Another approach to hiding the paragraph would be to add an inline style to the paragraph, with the value of display:none. The Interactivity API includes a wp-style directive that adds or removes inline styles from the element.

The directive follows the syntax data-wp-style--[css-property].

To use the display CSS property, you need to add the data-wp-style--display directive to the <p> element and set it to either none or block. You can’t use the context.isOpen directly as value because it returns true or false, but you can use it as a base for a derived state that will be computed from it.

The Interactivity API store has another property called state, where you can define the derived state or context using a getter function. Let’s create a display() getter. It will return none if context.isOpen is false and block when true.

store( 'donation-calculator', {
	state: {
		get display() {
			const context = getContext();
    		      return context.isOpen ? 'block' : 'none';
		}
	},
	actions: {
		toggle: () => {
			const context = getContext();
			context.isOpen = ! context.isOpen;
		}
	},
});

You can then use the display() getter in the data-wp-style--display directive, treating it like any other state property, referring to it as state.display. Let’s add it to the paragraph.

<p
	id="<?php echo esc_attr( $unique_id ); ?>"
	data-wp-style--display="state.display"
>
	<?php
	esc_html_e( 'Donation Calculator - hello from an interactive block!', 'donation-calculator' );
	?>
</p>

Creating a donation calculator

Let’s modify the block to create a donation calculator for a charity organization. It will have a form where site visitors can enter the amount they are willing to donate. The calculator will then calculate and display the number of trees the organization can plant for the specified donation. The block will allow the website admin to set the price of planting one tree, which will be used in the calculations.

There are a few steps that you can take:

  • define an attribute that will store the price (block.json)
  • add a control where the editor can set the price for planting one tree and include a visual preview of a form (edit.js)
  • make a few changes to the HTML markup of the block, such as adding a form, adding the Interactivity API directives etc. (render.php)
  • define an action and getter functions that will handle calculations (view.js)
  • add the style for the block (style.scss and editor.scss)

Adding price control in the editor

First, define a price attribute to store the price of planting a tree set by the person editing the page. Add the attributes property to the existing elements of the block.json. The price attribute will have a type of number and a default value which will be used by the calculator in case the editor won’t set the price.

"attributes": {
    "price": {
      "type": "number",
      "default": 15
    }
 },

Next, create the editor view for the block. Add this code to the edit.js file.

import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { __experimentalNumberControl as NumberControl, PanelBody
} from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {

	const { price } = attributes;
	const exampleDonationAmount = 1000;
        const trees = Math.floor( exampleDonationAmount / price );

	return (
		<div { ...useBlockProps() }>
			<InspectorControls>
				<PanelBody title={ __( 'Calculator settings' ) }>
					<NumberControl
						label={ __( 'Set the price for one tree' ) }
						help={ __( 'The value will be used for calculations.' ) }
						value={ price }
						min={ 1 }
						onChange={ 
							( value ) => 
								setAttributes( { 
									price: Number( value ) 
								} ) 
						}
					/>
				</PanelBody>
			</InspectorControls>
			<form className="calculator">
				<label for="contribution-value" className="calculator-label">{ __( 'Check the impact of your donation:' ) }</label>
				<div class="calculator-input">$
					<input disabled value={ exampleDonationAmount } className="calculator-input-form" type="number" id="contribution-value"/>
				</div>
				<output className="calculator-output">
				    {[
				    	__( 'Your ' ), 
				    	<span>${ exampleDonationAmount }</span>, 
				    	__( ' donation will enable us to plant ' ),
				    	<span>{ trees }</span>,
				    	__( ' trees.' )
				    ]}
				</output>
			</form>
		</div>
	);
}

You won’t be able to add the Interactivity API to the backend of the block in WordPress 6.5. It will serve as a preview of the form with some predefined data and a control for the price. The calculator will only work on the front end for site visitors.

Adding styles to the block

You also need to add some styles to the block. Go ahead and copy the following CSS and add it to the style.scss file in the block directory.

.wp-block-create-block-donation-calculator {
	box-sizing: border-box;
	background-color: #f2fcf6;
	color: #023a51;
	border: 3px solid #023a51;
	border-radius: 1.5rem;
	text-align: center;
	overflow: hidden;

	.calculator {
		padding: 3rem 2rem;

		&-input {
			margin: 1.25rem auto;
			font-size: 1.75rem;
			color: #023a51;
			display: flex;
			justify-content: center;
			align-items: center;

			&-form{
				padding: 0.5rem 1rem;
				margin-left: 0.5rem;
				border: 2px solid #023a51;
				border-radius: 1rem;
				background-color: #fff;
				font-size: 1.5rem;
				color: #023a51;
				max-width: 130px;
			}
		}
		&-output {
			display: none;

			&.show {
				display: block;
			}

			span {
				color: #f2fcf6;
				background: #0cab49;
				font-weight: bold;
				border-radius: 5px;
				padding: 0.25rem 0.5rem;
			}
		}
	}
}

Add the style for the backend (in the editor.scss) to make the info paragraph visible at all times in the editor.

.wp-block-create-block-donation-calculator  {
	.calculator-output {
		display: block;
	}
}

The editor view of the block: