I recently came across a website with a cool CSS effect on the homepage with words that smoothly transition between different variations.
Naturally, I had to peek under the hood. The site was built with WordPress, and the effect was done using a page builder. That got me thinking, “Hey, this would make a neat WordPress block! And I could build it without relying on any external libraries.”
I started prototyping a custom block, but quickly realized it made more sense to extend existing core blocks. It turned out to be a good example of combining several WordPress APIs to add custom behavior on top of what already exists:
- The Interactivity API
- The WP_HTML_Tag_Processor (a.k.a. “The HTML API”)
- The Formatting Toolbar API
- Javascript Modules
I set myself a challenge: create a production-ready version that could demonstrate the power of these combined APIs.
Table of Contents
The editor experience matters too
Here’s what makes WordPress special: it doesn’t just help you build great user experiences—it gives you the tools to build great editing experiences too. That word-switching effect? Sure, it’s 20 lines of JavaScript for the front end. But WordPress challenges you to think bigger: how will content creators actually work with this feature?
This is where WordPress shines as a development framework. While other platforms focus solely on the visitor experience, WordPress provides dedicated APIs for both sides of the equation:
For content editors, WordPress offers:
- The Format API to extend the editing toolbar with intuitive controls.
- Visual feedback that shows exactly what will animate.
- A familiar interface to format content.
For site visitors, WordPress delivers:
- The Interactivity API for smooth, performant animations without external libraries
- The HTML API for server-side processing and ensuring clean, semantic HTML
- Modern JavaScript modules that load efficiently
This word switcher project perfectly demonstrates this dual-focus philosophy. Every technical decision we make serves both audiences:
- Editors get a seamless way to mark switchable text without code
- Visitors see fast, smooth animations without slowing down the site.
- Developers get clean, maintainable code that leverages WordPress core
This is the WordPress way: don’t just solve for the frontend, solve for the entire content lifecycle. And you don’t have to start from scratch; you can build on top of what’s already there. Let’s see how these APIs work together to make that possible.
What we’re building
For this article we’ll create a functionality that allows editors to mark certain text areas where words will dynamically switch. For example, the text “I’m a developer, designer, creator” can be marked so that the words separated by commas cycle through with an animation on the frontend—all built with native WordPress tools and no external dependencies.
Prerequisites
- WordPress development environment (WordPress Studio, wp-env, etc.)
- Node.js and npm installed
- Basic knowledge of React and WordPress block development
- Familiarity with PHP
The final code for the project is at https://github.com/wptrainingteam/word-switcher-core-blocks where you can also follow along with the different steps explained in this article by checking the commits done in the repo.
Overview: The Four Core Technologies
For our project, we’ll use the Interactivity API’s data attributes (directives) to make specific parts of the HTML respond to JavaScript-driven state changes on the frontend. Before returning the block’s markup to the browser, we look for text that editors have specially marked using our custom format (registered via the Format API) inside paragraph and heading blocks. Whenever such marked text is found, we use the HTML API to inject the appropriate Interactivity API directives into just those sections. As a result, only the intended words or phrases smoothly animate on the frontend—cleanly connecting editor choices to interactive effects for visitors.
Before we dive into the code, let’s understand the key WordPress APIs that make this project possible:
Interactivity API
The Interactivity API is WordPress’s declarative system for adding dynamic behaviors to the frontend. Instead of writing imperative JavaScript that manipulates the DOM directly, you declare behaviors through HTML attributes (data-wp-*). This API manages state, handles reactivity, and provides a clean separation between markup and behavior.
JavaScript Modules
WordPress now supports native ES modules, allowing for better code organization and performance. Modules load asynchronously, can be deferred, and provide better tree-shaking capabilities. We’ll use modules to load our interactive frontend code efficiently in the browser.
Format API
The Format API allows developers to extend the rich text editor with custom inline formatting options. Just like bold or italic formatting, you can create custom formats that appear in the toolbar. In our project, we’ll use this to let editors mark specific text as “switchable” — text that will animate between different words on the frontend.
HTML API (WP_HTML_Tag_Processor)
WordPress’s HTML API provides a safe, reliable way to parse and modify HTML content on the server side. Unlike regex or string manipulation, it understands HTML structure and handles edge cases properly. We’ll use this to process the marked content and prepare it for frontend interactivity.
Setting Up the Plugin Structure
Let’s start by creating our plugin structure. In this project we’ll extend WordPress’s core blocks (paragraphs, headings) with dynamic functionality, rather than creating entirely new blocks.
First, create a new plugin directory in your WordPress installation:
cd wp-content/plugins
mkdir word-switcher-core-blocks
cd word-switcher-core-blocks
Create the main plugin file plugin.php:
<?php
/**
* Plugin Name: Word Switcher Core Blocks
* Description: Example block scaffolded with Create Block tool.
* Version: 0.1.0
* Requires at least: 6.7
* Requires PHP: 7.4
* Author: The WordPress Contributors
* License: GPL-2.0-or-later
* Text Domain: word-switcher
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
// We'll add our functions here as we build
With this file created, you can now go to the WordPress Admin, navigate to the Plugins screen, and activate your “Word Switcher Core Blocks” plugin.
Next, create a package.json file with these build scripts and run npm install to install the dependencies:
{
"name": "word-switcher",
"version": "0.1.0",
"scripts": {
"build": "wp-scripts build --experimental-modules",
"start": "wp-scripts start --experimental-modules"
},
"devDependencies": {
"@wordpress/scripts": "^30.15.0"
}
}
The --experimental-modules flag enables support for modern JavaScript modules in the build process, which is required for the Interactivity API to function correctly.
Next, set up your folder structure by creating the following directory structure inside your plugin folder, along with the initial files that we’ll populate throughout this article:
├── resources
│ ├── css
│ │ └── word-switcher-styles.scss # word-switcher styles and animations
│ └── js
│ ├── register-format-type.js # Handles custom text formatting (Format API)
│ └── word-switcher-store.js # Manages state and transitions (Interactivity API)
├── plugin.php # Main plugin logic
└── package.json # Build tools and dependencies
└── webpack.config.js # Custom webpack configuration
We’ll be working in these files step by step as we implement the core functionality.
Implementing the Format API
Let’s start by adding a toolbar button using the Format API. This will let editors highlight text (with words separated by commas) and mark it for word switching. We’ll wrap this text in a special tag, so we can detect and process it later in PHP.
Understanding the Format API
The Format API extends WordPress’s rich text editor by adding custom inline formatting options to the toolbar. When you select text and apply bold formatting, you’re using the Format API. We’ll create a similar experience for marking “switchable” text.
Key concepts:
- Format Types: Custom formats that can be applied to selected text
- Toolbar Buttons: UI controls that appear when text is selected
- HTML Markup: The actual tags and classes applied to the content
Creating the Format Type
Create the file resources/js/register-format-type.js:
import { toggleFormat, registerFormatType } from "@wordpress/rich-text";
import { RichTextToolbarButton } from "@wordpress/block-editor";
const WORD_SWITCHER_FORMAT_TYPE = "word-switcher/format-type-delimiter";
registerFormatType(WORD_SWITCHER_FORMAT_TYPE, {
title: "Word Switcher",
tagName: "span",
className: "word-switcher",
edit: ({ isActive, onChange, value }) => {
return (
<RichTextToolbarButton
icon="update"
title="Mark as Word Switcher Area"
onClick={() => {
onChange(
toggleFormat(value, {
type: WORD_SWITCHER_FORMAT_TYPE,
}),
);
}}
isActive={isActive}
/>
);
},
});
This creates a toolbar button that wraps selected text in a <span class="word-switcher"> tag. When editors select text like “developer, designer, creator” and click this button, the Format API wraps it in our custom span tag, preparing it for server-side processing.
We need to create a custom webpack configuration file to configure our custom entry point. Create the following webpack.config.js:
const [
defaultConfigNonModule,
defaultConfigModule,
] = require("@wordpress/scripts/config/webpack.config");
const path = require("path");
module.exports = [
// Non Module config
{
...defaultConfigNonModule,
entry: {
"js/register-format-type": path.resolve(
process.cwd(),
"resources/js",
"register-format-type.js"
)
},
},
// Module config
{
...defaultConfigModule
},
];
The process of creating a custom webpack configuration file for custom entry points, especially when working with JavaScript modules, has been covered in other articles of the WordPress Developer Blog. Check Building a light/dark toggle with the Interactivity API > Configuring webpack.
Now that we’ve set up webpack.config.js, let’s run npm start to get both build/js/register-format-type.js (the compiled script) and build/js/register-format-type.asset.php (which includes dependency and version details).
Register and enqueue this script in plugin.php by doing:
function word_switcher_core_blocks_register_assets() {
$dir = plugin_dir_path( __FILE__ );
define( 'WS_FORMAT_SCRIPT', 'word-switcher-core-blocks-register-format-type' );
// Register the Format API script for the editor.
$script_asset = require "$dir/build/js/register-format-type.asset.php";
wp_register_script(
WS_FORMAT_SCRIPT,
plugins_url( 'build/js/register-format-type.js', __FILE__ ),
$script_asset['dependencies'],
$script_asset['version'],
true
);
}
add_action('init', 'word_switcher_core_blocks_register_assets');
// Enqueue format type in the editor
function word_switcher_core_blocks_enqueue_block_editor_assets() {
wp_enqueue_script( WS_FORMAT_SCRIPT );
}
add_action('enqueue_block_editor_assets', 'word_switcher_core_blocks_enqueue_block_editor_assets');
We use the enqueue_block_editor_assets action hook to enqueue assets specifically for the block editor in the admin screen.
Now we should have a new “Mark as Word Switcher Area” formatting tool that it will wrap the selected text into a custom span with the word-switcher class
At this point, your plugin.php, resources/js/register-format-type.js and webpack.config.js files should look like the ones in this commit
Server-Side HTML Processing with the HTML API
With our content marked with special <span> element by the Format API, we can now process this HTML on the server before sending it to the browser. This is where WordPress’s HTML API shines.
Understanding the HTML API
The HTML API (specifically WP_HTML_Tag_Processor) is WordPress’s solution for safely parsing and modifying HTML. Traditional approaches using regex or string manipulation are error-prone and can break with complex HTML. The HTML API:
- Understands HTML structure: It knows about tags, attributes, and nesting
- Handles edge cases: Properly deals with quotes, special characters, and malformed HTML
- Provides safe manipulation: Methods for adding/modifying attributes without breaking markup
- Supports bookmarking: Can mark positions in the HTML for later reference
Processing Core Block Markup with the HTML API
To intercept and enhance core block output, we’ll use the render_block_{$this->name} filter. This filter allows us to modify a block’s HTML content during server-side rendering. By hooking into this filter for core/paragraph and core/heading blocks, we can inject our Interactivity API directives functionality directly into the rendered markup
Let’s start by adding the following code to your plugin.php file:
add_filter( 'render_block_core/paragraph', 'word_switcher_core_blocks_render_block', 10, 2 );
add_filter( 'render_block_core/heading', 'word_switcher_core_blocks_render_block', 10, 2 );
function word_switcher_core_blocks_render_block($block_content, $block) {
// ... code for processing and modifying block content.
return $block_content;
}
Begin the word_switcher_core_blocks_render_block function by checking if its content does not include a <span class="word-switcher">. In that case, simply return $block_content without further processing.
// Check if content contains our word-switcher markup
if (strpos($block_content, 'class="word-switcher"') === false) {
return $block_content;
}
Initialize the HTML API processor to parse the block’s markup and start navigating to the first node which should be the block’s wrapper. We’ll set a bookmark to come back here later.
$processor = new WP_HTML_Tag_Processor($block_content);
// Find the first tag (the block wrapper)
if (!$processor->next_tag()) {
return $block_content;
}
$processor->set_bookmark("parent");
$words = [];
Now we iterate through the HTML to find the right element, extract its content and add the Interactivity API directives:
- Locates word-switcher spans: Uses the HTML API’s tag filtering to find spans marked by the Format API
- Injects Interactivity directives: Adds
data-wp-textto bind content to the current word state, anddata-wp-class--fadefor animation control - Extracts word variations: Parses the comma-separated text content (e.g., “powerful, flexible,
// Find all spans with word-switcher class
while ($processor->next_tag(['tag_name' => 'span', 'class_name' => 'word-switcher'])) {
// Add Interactivity API directives
$processor->set_attribute("data-wp-text", "state.currentWord");
$processor->set_attribute("data-wp-class--fade", "context.isFading");
// Extract the comma-separated words
if ($processor->next_token()) {
$text_content = $processor->get_modifiable_text();
if ($text_content) {
$words = array_filter(array_map('trim', explode(',', $text_content)));
}
}
}
Now, we jump back to the parent element using our previously set bookmark. Here, we attach the Interactivity API attributes:
data-wp-interactivelinks the element to our store,data-wp-contextinitializes its local state, anddata-wp-initregisters a callback to run when the element mounts to the DOM.
// Return to parent and add Interactivity API attributes
$processor->seek("parent");
$processor->set_attribute("data-wp-interactive", "devblog/word-switcher-core-blocks");
$processor->set_attribute("data-wp-init", "callbacks.init");
$processor->set_attribute("data-wp-context", json_encode([
"words" => $words,
"currentIndex" => 0,
"isFading" => false
]));
And we finally return the modified block’s markup
return $processor->get_updated_html();
With these modifications, the render filter function in plugin.php should look like this.
Creating the Interactivity API Store
With the editor markup and necessary directives in place, let’s move on to building the JavaScript behavior that will power our feature using the Interactivity API.
Understanding the Interactivity API
The Interactivity API is WordPress’s answer to modern JavaScript frameworks, but tailored for the block editor ecosystem. Key concepts for this API are:
- Stores: JavaScript objects that hold state and logic
- Directives: HTML attributes (
data-wp-*) that connect HTML to JavaScript behavior - Context: Local state for specific HTML elements
- State: Global reactive data that triggers UI updates
The Interactivity API Store
Create resources/js/word-switcher-store.js to define the interactive behavior:
import { store, getContext } from "@wordpress/interactivity";
store("devblog/word-switcher-core-blocks", {
state: {
get currentWord() {
const context = getContext();
return context.words[context.currentIndex];
},
},
callbacks: {
init() {
const context = getContext();
setInterval(() => {
context.isFading = true;
setTimeout(() => {
context.isFading = false;
context.currentIndex =
(context.currentIndex + 1) % context.words.length;
}, 500);
}, 5000);
},
},
});
This JavaScript store connects directly to the HTML directives we added earlier:
- Store namespace: The string
"devblog/word-switcher-core-blocks"is used as the store’s unique identifier. It matches thedata-wp-interactiveattribute in your HTML, giving that part of the DOM access to this Interactivity API store. currentWordderived state computes which word to display, powering thedata-wp-text="state.currentWord"directiveinitcallback triggered bydata-wp-init="callbacks.init", starts the word-switching cycle- Animation flow: Every 5 seconds,
context.isFadingtoggles (controlling ourdata-wp-class--is-fadingdirective), thencurrentIndexadvances to the next word
The Interactivity API creates a reactive system where JavaScript state changes automatically update the DOM through our directives—no manual DOM manipulation needed
Configuring JavaScript Modules
To properly bundle and load our Interactivity API code, we need to configure it to use modern JavaScript modules instead of traditional scripts.
Understanding JavaScript Modules in WordPress
JavaScript modules offer several advantages over traditional scripts:
- Better performance: Modules can be loaded asynchronously and deferred
- Dependency management: Import/export syntax makes dependencies explicit
- Tree shaking: Unused code can be eliminated during build
- Native browser feature: No need for additional runtime libraries
- Scope isolation: Each module has its own scope, preventing global pollution
WordPress 6.5 introduced Script Modules support to align with modern web standards and improve performance.
The build with JavaScript modules
We need to add another entry point in the project’s custom webpack config for resources/js/word-switcher-store.js. Add the following module config to webpack.config.js (to the part of file with the comment “Module config”)
// Module config
{
...defaultConfigModule,
...{
entry: {
"js/word-switcher-store": path.resolve(
process.cwd(),
"resources/js", "word-switcher-store.js"
),
},
},
},
Run the build process to compile your modules:
npm run build
After running the build, you should see these files in the build directory:
build/js/word-switcher-store.js– The compiled interactive module (JS Module)build/js/word-switcher-store.asset.php– Dependencies metadatabuild/js/register-format-type.js– The compiled Format API code (Non JS Module)build/js/register-format-type.asset.php– Format API dependencies
If you look at the built files, you’ll see that build/js/word-switcher-store.js is compiled as a JS module and its dependencies declared with import are loaded asynchronously by the browser as needed. In contrast, build/js/register-format-type.js is bundled as a regular JavaScript file and its dependencies are either included directly in the bundle or mapped to appropriate WordPress script handles that are already enqueued and available.
Registering JS Modules Assets
To properly load build/js/word-switcher-store.js as a JS Module in the frontend, we need to register it as a JS Module using wp_register_script_module
Add the following code to the end of your word_switcher_core_blocks_register_assets function in your plugin.php file:
function word_switcher_core_blocks_register_assets() {
// ... previous plugin asset registration code goes here.
define( 'WS_IAPI_SCRIPT', 'word-switcher-core-blocks-interactivity-api' );
// Register the Interactivity API script for the editor.
$script_interactivity_api_asset = require "$dir/build/js/word-switcher-store.asset.php";
wp_register_script_module(
WS_IAPI_SCRIPT,
plugins_url( 'build/js/word-switcher-store.js', __FILE__ ),
$script_interactivity_api_asset['dependencies'],
$script_interactivity_api_asset['version']
);
//TO-DO: Register styles for animations
}
add_action('init', 'word_switcher_core_blocks_register_assets');
Our assets strategy for this project is:
- Traditional script for the Format API (editor functionality)
- Script module for the Interactivity API (frontend behavior)
- Conditional loading of frontend assets only when needed
With our JS module built and registered, we want to ensure it’s only loaded on the frontend when it’s actually needed—specifically, for core paragraph or heading blocks containing a <span class="word-switcher">. To accomplish this, simply enqueue the script module at the end of our word_switcher_core_blocks_render_block function, since this filter only triggers for the relevant blocks and content.
function word_switcher_core_blocks_render_block( $block_content, $block ) {
// ... previous render block logic goes here.
if ( ! is_admin() ) {
wp_enqueue_script_module( WS_IAPI_SCRIPT );
}
return $processor->get_updated_html();
}
At this point, the changes on your plugin.php and webpack.config.js files should look like the ones in this commit
Adding Styles and Animation
With everything else set up, it’s time for the most fun part. Choose an animation effect and define the styles to bring our word switcher to life visually.
Creating CSS Animations
Create resources/css/word-switcher-styles.scss with the following code:
.word-switcher {
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
display: inline-block;
transform-origin: center;
position: relative;
opacity: 1;
will-change: opacity, transform;
transform: scale(1) rotateY(0deg);
&.fade {
opacity: 0;
transform: scale(0.7) rotateY(90deg);
}
&:not(.fade) {
opacity: 0;
transform: scale(0.7) rotateY(-90deg);
animation: flipIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes flipIn {
0% {
opacity: 0;
transform: scale(0.7) rotateY(-90deg);
}
60% {
transform: scale(1.05) rotateY(10deg);
}
100% {
opacity: 1;
transform: scale(1) rotateY(0deg);
}
}
}
The CSS provides smooth transitions between words, creating a polished user experience. The fade class is toggled by the Interactivity API, triggering the opacity and transform animations.
Now, add a new entry point in the project’s custom webpack config. Add the following to to webpack.config.js:
// Non Module Config
{
...defaultConfigNonModule,
entry: {
// js/register-format-type entry point
"css/word-switcher-styles": path.resolve(
process.cwd(),
"resources/css", "word-switcher-styles.scss"
),
},
},
Run the build again to compile your styles:
npm run build
You should now see build/css/word-switcher-styles.css in your build directory.
Register the styles
function word_switcher_core_blocks_register_assets() {
// ... previous plugin asset registration code goes here.
define( 'WS_STYLES', 'word-switcher-core-blocks-styles' );
wp_register_style(
WS_STYLES,
plugins_url( 'build/css/word-switcher-styles.css', __FILE__ ),
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'build/css/word-switcher-styles.css' )
);
}
add_action('init', 'word_switcher_core_blocks_register_assets');
Enqueue the styles at the end of word_switcher_core_blocks_render_block as this filter only runs for relevant content with <span class="word-switcher">.
function word_switcher_core_blocks_render_block( $block_content, $block ) {
// ... previous render block logic goes here.
if ( ! is_admin() ) {
wp_enqueue_script( WS_FORMAT_SCRIPT );
wp_enqueue_style( WS_STYLES );
}
return $processor->get_updated_html();
}
At this point, the changes on your plugin.php, resources/css/word-switcher-styles.scss
andwebpack.config.js files should look like the ones in this commit
Testing our Implementation
Now let’s test the complete workflow to ensure everything works together.
Build the plugin:
npm run build
Activate the plugin:
- Go to WordPress Admin Plugins
- Find “Word Switcher Core Blocks”
- Click “Activate”
Mark some text separated by commas as a “Word Switcher Area”:
- Go to the “Blog Home” template
- Change the heading to include some text separated by commas
- Mark those words with our custom format “Mark as Word Switcher Area”
- Save and check the effect on the frontend
Conclusion
This Word Switcher Core Blocks project showcases the power of modern WordPress development by combining four different groups of APIs. This approach also demonstrates that the block editor is not just a content creation tool, but a framework for building rich, interactive experiences.
By understanding and leveraging these APIs together, developers can:
- Create engaging user experiences without sacrificing performance
- Extend WordPress in maintainable, future-proof ways
- Build features that feel native to the editing experience
- Deliver dynamic content while maintaining accessibility and SEO
These techniques and APIs make it easy to build anything from simple text tweaks to rich interactive components, by taking full advantage of WordPress’s built-in capabilities instead of reinventing core functionality. There’s no need to start from scratch, and once you’ve built it, you can reuse it across any WordPress setup.
Resources
Props to @bph and @greenshady for reviewing the article and sharing their feedback.
Leave a Reply