Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e88d3a9
FormToggle: Update disabled styles (#77208)
mirka Apr 10, 2026
4c532a8
Calendar: fix disabled styles (#77138)
oandregal Apr 10, 2026
0ab01c1
Textarea: remove unnecessary styles (#77221)
oandregal Apr 10, 2026
2abcdea
Search Block: Ensure color settings apply to input field when button …
dpmehta Apr 10, 2026
78404d4
iAPI Docs: Fix typos, code errors, and inaccuracies in the documentat…
DAreRodz Apr 10, 2026
763b525
Connectors: don't clobber third-party custom render in registerDefaul…
superdav42 Apr 10, 2026
a3f2010
Guidelines: Improve guideline revision UX (#76560)
aswasif007 Apr 10, 2026
f661da5
ui/`Dialog`: update Header layout, refactor Title to use Text (#77161)
ciampo Apr 10, 2026
c6f0d2d
ui/docs: add additional global css setup instructions (#77228)
ciampo Apr 10, 2026
dffaa5b
ui/VisuallyHidden: Standardize composition pattern (#77190)
ciampo Apr 10, 2026
801d249
ui: expose `container` portal prop on all overlay Popup components (#…
ciampo Apr 10, 2026
ef54ab2
Components: Use `--wpds-cursor-control` for interactive controls (Sas…
mirka Apr 10, 2026
6ea50fc
Card: Remove redundant margin reset from Card.Title (#77187)
ciampo Apr 10, 2026
1e307ab
Theme: Rename typography tokens to use "typography" prefix (#76912)
aduth Apr 10, 2026
1957df1
UI: Normalize render prop and ref forwarding patterns (#77160)
ciampo Apr 10, 2026
4733e5c
Ensure "Retry" button is stable during retries (#77234)
alecgeatches Apr 10, 2026
a6bfd3e
RTC: Improve array attribute stability when structural changes occur …
alecgeatches Apr 10, 2026
bc6d247
Env: Fix loopback requests when running on non-default ports (#77057)
nerrad Apr 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions docs/reference-guides/interactivity-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ Use the following links to locate the topic you're interested in. If you have ne

- **[Requirements](#requirements-of-the-interactivity-api):** Check this section before you start creating your interactive blocks with the Interactivity API.
- **[Quick Start Guide](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/iapi-quick-start-guide/):** Get a custom block using the Interactivity API up and running in less than one minute.
- **[Tutorial: A first look at the Interactivity API](https://developer.wordpress.org/news/2024/04/11/a-first-look-at-the-interactivity-api/)** This article from the [WordPress Developer Blog](https://developer.wordpress.org/news/) is a great way to get introduced to the Interactivity API.
- **[Core Concepts](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/core-concepts/)** Gain a better understanding of concepts and mental models related to Interactivity API development from this section.
- **[Tutorial: A first look at the Interactivity API](https://developer.wordpress.org/news/2024/04/11/a-first-look-at-the-interactivity-api/):** This article from the [WordPress Developer Blog](https://developer.wordpress.org/news/) is a great way to get introduced to the Interactivity API.
- **[Core Concepts](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/core-concepts/):** Gain a better understanding of concepts and mental models related to Interactivity API development from this section.
- **[Directives and Store](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/directives-and-store/):** To take a deep dive into how the API works internally, the list of Directives, and how the Store works.
- **[Docs and Examples](#docs-examples):** Additional resources to learn/read more about the Interactivity API.
- **[Docs and Examples](#docs--examples):** Additional resources to learn/read more about the Interactivity API.

To get a deeper understanding of what the Interactivity API is or find answers to questions you may have about this standard, check the following resources:

Expand Down Expand Up @@ -60,8 +60,9 @@ import { store } from '@wordpress/interactivity';

To indicate that the block [supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) the Interactivity API features, add `"interactivity": true` to the `supports` attribute of the block's `block.json` file.

In `block.json`:

```json
// block.json
"supports": {
"interactivity": true
},
Expand All @@ -73,24 +74,27 @@ Refer to the [`interactivity` support property docs](https://developer.wordpress

The Interactivity API provides the `@wordpress/interactivity` Script Module. JavaScript using the Interactivity API should be implemented as Script Modules so they can depend on `@wordpress/interactivity`. [Script Modules have been available since WordPress 6.5](https://make.wordpress.org/core/2024/03/04/script-modules-in-6-5/). Blocks can use [`viewScriptModule` block metadata](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script-module) to enqueue their Script Modules easily:

In `block.json`:

```json
// block.json
{
...
"viewScriptModule": "file:./view.js"
"viewScriptModule": "file:./view.js"
}
```

The use of `viewScriptModule` also requires the `--experimental-modules` flag for both the [`build`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#build) and [`start`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#start) scripts of `wp-scripts` to ensure a proper build of the Script Modules.

> **Note:** If you scaffolded your block using the [`@wordpress/create-block-interactive-template`](https://www.npmjs.com/package/@wordpress/create-block-interactive-template), this flag is already included in your `package.json` scripts and no manual configuration is needed.

If you need to add it manually, update your `package.json`:

```json
// package.json
{
"scripts": {
...
"scripts": {
"build": "wp-scripts build --experimental-modules",
"start": "wp-scripts start --experimental-modules"
}
}
```

#### Add `wp-interactive` directive to a DOM element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ When `clientNavigationDisabled` is `true`:

The Interactivity API router includes built-in feedback during navigation:

- **Loading animation**: A progress bar that appears at the top of the page during navigation. The bar appears after a short delay (400ms) if navigation hasn't completed yet. This 400ms delay is introduced to avoid showing the animation if the page has been sucessfully prefetched or in very fast connections.
- **Loading animation**: A progress bar that appears at the top of the page during navigation. The bar appears after a short delay (400ms) if navigation hasn't completed yet. This 400ms delay is introduced to avoid showing the animation if the page has been successfully prefetched or in very fast connections.
- **Screen reader announcements**: Accessibility announcements for navigation progress.

In some cases, you may want to disable these:
Expand Down Expand Up @@ -806,7 +806,12 @@ const { actions } = store( 'myPlugin', {
} );
// Navigate to the same page, bypassing the cache
// to reflect the updated content.
yield navigate( window.location.href, { force: true } );
const { actions: routerActions } = yield import(
'@wordpress/interactivity-router'
);
yield routerActions.navigate( window.location.href, {
force: true,
} );
},
},
} );
Expand Down Expand Up @@ -989,11 +994,11 @@ This approach ensures that:

When `navigate()` actually renders the new page, the router toggles style sheets on and off:

- **Activating styles**: For each style sheet that belongs to the target page, the router removes the `media="preload"` override (or restores the original `media` attribute if one was specified). This causes the browser to apply those styles.
- **Activating styles**: For each style sheet that belongs to the target page, the router restores the original `media` attribute (reverting the `media="preload"` override set during prefetching) and sets `sheet.disabled = false`. This causes the browser to apply those styles.

- **Deactivating styles**: For each style sheet that was in the current page but not the target page, the router sets `media="preload"`. This disables the styles without removing the element from the DOM.
- **Deactivating styles**: For each style sheet that was in the current page but not the target page, the router sets `sheet.disabled = true`. This disables the styles without removing the element from the DOM.

By keeping deactivated style elements in the DOM (rather than removing them), the router can quickly reactivate them if the user navigates back. The styles are already loaded and parsed; they just need to be enabled.
By keeping deactivated style elements in the DOM (rather than removing them), the router can quickly reactivate them if the user navigates back. The styles are already loaded and parsed; they just need to be re-enabled.

### Script module handling

Expand Down Expand Up @@ -1084,9 +1089,9 @@ When WordPress renders a page with interactive elements, it embeds server-provid
{
"state": {
"myPlugin": {
"cartItemCount": 3,
"cartItemCount": 3
}
}
},
"config": {
"myPlugin": {
"userLoggedIn": true
Expand All @@ -1111,11 +1116,11 @@ Local context is embedded directly in the `data-wp-context` attribute of element

When the router fetches a new page, it extracts these types of server data:

1. **Global state**: The router finds the `<script type="application/json">` element with ID `wp-script-module-data-@wordpress/interactivity` and parses its JSON content to extrat its `state` property. This state comes from `wp_interactivity_state` is stored in the internal in-memory page cache entry.
1. **Global state**: The router finds the `<script type="application/json">` element with ID `wp-script-module-data-@wordpress/interactivity` and parses its JSON content to extract its `state` property. This state comes from `wp_interactivity_state` is stored in the internal in-memory page cache entry.

2. **Local context**: Context values are embedded in the virtual DOM representation of each router region. When a region's HTML is converted to vDOM, the `data-wp-context` attributes are preserved and will be processed during rendering.

3. **Config**: The router finds the `<script type="application/json">` element with ID `wp-script-module-data-@wordpress/interactivity` and parses its JSON content to extrat its `config` property. This configuration comes from `wp_interactivity_config` and is stored in the internal in-memory page cache entry.
3. **Config**: The router finds the `<script type="application/json">` element with ID `wp-script-module-data-@wordpress/interactivity` and parses its JSON content to extract its `config` property. This configuration comes from `wp_interactivity_config` and is stored in the internal in-memory page cache entry.

**Merging server data during navigation**

Expand Down Expand Up @@ -1200,12 +1205,13 @@ When `navigate()` is called (for example, on link click):
2. If not already prefetched, the fetch process from Phase 1 runs now.
3. The router waits for the page to be ready (fetch complete, styles loaded).
4. A loading indicator may appear if the wait exceeds a threshold (400ms).
5. The rendering phase begins (wrapped in a batch for efficiency):
- Script modules for the new page are executed.
- Server state is merged with client state.
- Each router region is updated with its new virtual DOM.
- Regions with `attachTo` that don't exist are created and appended.
5. The rendering phase begins:
- Styles are activated/deactivated as needed.
- Script modules for the new page are executed.
- In a batch for efficiency:
- Server state is merged with client state.
- Each router region is updated with its new virtual DOM.
- Regions with `attachTo` that don't exist are created and appended.
- The document title is updated.
6. Browser history is updated (pushState or replaceState).
7. Screen reader announcement is made for accessibility.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ _Please, visit the [Understanding global state, local context, derived state and
Let's imagine adding a button that can delete all fruits:

```html
<button data-wp-on--click="actions.deleteFruits">
Delete all fruits
</button>
<button data-wp-on--click="actions.deleteFruits">Delete all fruits</button>
```

```javascript
Expand Down Expand Up @@ -322,6 +320,7 @@ wp_interactivity_state( 'myFruitPlugin', array(
'fruits' => array( __( 'Apple' ), __( 'Banana' ), __( 'Cherry' ) ),
'shoppingList' => array( __( 'Apple' ), __( 'Cherry' ) ),
// ...
) );
?>

<div data-wp-interactive="myFruitPlugin">
Expand Down Expand Up @@ -420,24 +419,28 @@ const { state } = store( 'myFruitPlugin', {
</template>
```

Serializing information from the server can also be useful in other scenarios, such as passing Ajax/REST-API URLs and nonces.
Passing non-reactive, static data from the server—such as Ajax/REST-API URLs and nonces—is also a common need. Because these values never change at runtime, they belong in the **config** rather than the global state. Use `wp_interactivity_config()` on the server and `getConfig()` on the client.

```php
wp_interactivity_state( 'myPlugin', array(
wp_interactivity_config( 'myPlugin', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
));
```

```js
const { state } = store( 'myPlugin', {
import { store, getConfig } from '@wordpress/interactivity';

store( 'myPlugin', {
actions: {
*doSomething() {
const { ajaxUrl, nonce } = getConfig();

const formData = new FormData();
formData.append( 'action', 'do_something' );
formData.append( '_ajax_nonce', state.nonce );
formData.append( '_ajax_nonce', nonce );

const data = yield fetch( state.ajaxUrl, {
const data = yield fetch( ajaxUrl, {
method: 'POST',
body: formData,
} ).then( ( response ) => response.json() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ The Interactivity API uses a fine-grained reactivity system. Here's how it works

Let's break down these concepts by reviewing the previous example:

```javascript
```js
const { state } = store( 'myInteractivePlugin', {
state: {
isVisible: false,
Expand Down Expand Up @@ -245,18 +245,18 @@ Unlike many other reactive frameworks, **the Interactivity API does not require

For example, you can push a new item to an array like this:

```javascript
```js
const { state } = store( 'myArrayPlugin', {
state: {
list: [ 'item 1', 'item 2' ],
},
actions: {
addItem() {
// Right:
// Preferred — direct mutation:
state.list.push( 'new item' );

// Wrong:
state.list = [ ...state.list, 'new item' ]; // Don't do this!
// Unnecessary — this works, but it's more complex and can cause unnecessary rerenders.
state.list = [ ...state.list, 'new item' ];
},
},
} );
Expand All @@ -281,7 +281,9 @@ Here's an example of how you might use `data-wp-watch`:
</div>
```

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

store( 'myCounterPlugin', {
actions: {
increment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,14 @@ You should use local context when:
In this example, there is a single interactive block that shows a counter and can increment it. By using local context, each instance of this block will have its own independent counter, even if multiple blocks are added to the page.

```php
<?php
$context = array( 'counter' => 0 );
?>

<div
data-wp-interactive="myCounterPlugin"
<?php echo get_block_wrapper_attributes(); ?>
data-wp-context='{ "counter": 0 }'
<?php echo wp_interactivity_data_wp_context( $context ); ?>
>
<p>Counter: <span data-wp-text="context.counter"></span></p>
<button data-wp-on--click="actions.increment">Increment</button>
Expand Down Expand Up @@ -753,7 +757,7 @@ When using region-based navigation, it's crucial to ensure that your interactive

`getServerState()` allows you to subscribe to changes in the **global state** that occur during client-side navigation. This function is analogous to `getServerContext()`, but it works with the global state instead of the local context.

The `getServerState()` function returns a read-only reactive object. This means that any [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) you have defined that watch the returned object will only trigger when the value returned by the function changes. If the value remains the same, the callback will not re-trigger.
The `getServerState()` function returns a snapshot (deep clone) of the server-provided global state. This means that any [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) you have defined that watch the returned object will trigger on every client-side navigation event, regardless of whether the values have actually changed. Mutating the returned object has no effect on the actual server state, since it is a clone.

Let's consider a quiz that has multiple questions. Each question is a separate page. When the user navigates to a new question, the server provides the new question and the time left to answer all the questions.

Expand Down Expand Up @@ -802,7 +806,7 @@ _Note: Actions that need to call synchronous event methods like `event.preventDe

`getServerContext()` allows you to subscribe to changes in the **local context** that occur during client-side navigation. This function is analogous to `getServerState()`, but it works with the local context instead of the global state.

The `getServerContext()` function returns a read-only reactive object. This means that any [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) you have defined that watch the returned object will only trigger when the value returned by the function changes. If the value remains the same, the callback will not re-trigger.
The `getServerContext()` function returns a snapshot (deep clone) of the server-provided local context. This means that any [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) you have defined that watch the returned object will trigger on every client-side navigation event, regardless of whether the values have actually changed. Mutating the returned object has no effect on the actual server context, since it is a clone.

Consider a quiz that has multiple questions. Each question is a separate page. When the user navigates to a new question, the server provides the new question and the time left to answer all the questions.

Expand Down Expand Up @@ -852,8 +856,8 @@ Whenever you have interactive blocks that rely on global state or local context

### Best Practices for using `getServerState()` and `getServerContext()`

- **Read-Only References:** Both `getServerState()` and `getServerContext()` return read-only objects. You can use those objects to update the global state or local context.
- **Callback Integration:** Incorporate these functions within your store [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) to react to state and context changes. Both `getServerState()` and `getServerContext()` return reactive objects. This means that their watch callbacks will only trigger when the value of a property changes. If the value remains the same, the callback will not re-trigger.
- **Snapshot References:** Both `getServerState()` and `getServerContext()` return snapshot (deep clone) objects. Mutating them has no effect on the actual server state or context. You can read those objects to update the global state or local context.
- **Callback Integration:** Incorporate these functions within your store [callbacks](/docs/reference-guides/interactivity-api/directives-and-store.md#accessing-data-in-callbacks) to react to state and context changes. Note that callbacks watching `getServerState()` and `getServerContext()` will trigger on every client-side navigation event, regardless of whether the values have changed.

## Config

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ const { state } = store( 'myCounterPlugin', {
product: 2,
} as State, // Casts the entire state manually.
actions: {
increment() {
state.counter * state.product;
multiply() {
state.counter *= state.product;
},
},
} );
Expand Down Expand Up @@ -452,7 +452,7 @@ This also means that you can use your async actions in external functions, and T
```ts
const someAsyncFunction = async () => {
// This works fine and it's correctly typed.
await actions.delayedIncrement( 2000 );
await actions.delayedIncrement();
};
```

Expand Down
Loading
Loading