WordPress Logo WordPress 初めての Interactivity API

WordPress 6.5 から登場した Interactivity API は、テーマやプラグインに JavaScript をほとんど書かずに、インタラクティブな動きを追加できる新しい仕組みです。

以下では、ディレクティブやストアといった基礎的なことから始めて、カウンターやトグルボタンなどの簡単なブロックを作りながら基本的な使い方を解説しています。

関連ページ:Interactivity API で作るカテゴリー投稿フィルターブロック

更新日:2025年08月16日

作成日:2025年8月11日

Interactivity API の概要

Interactivity API は、WordPress 6.5 で導入された新しい仕組みで、クライアントサイドの JavaScript を用いて、ブロックやページにインタラクティブな機能を追加するための公式かつ標準化された方法です。

従来、WordPress に動的な振る舞いを加えるには、開発者が個別に JavaScript を記述したり、Ajax や REST API を利用して複雑な処理を実装したりする必要がありましたが、Interactivity API を使えば、統一された方法でクライアントとサーバー間のデータ連携を実現できます。また、WordPress コアに準拠した標準機能であるため、将来的な保守性や互換性にも優れています。

この API の最大の特徴は、サーバー側のデータとクライアントサイドの振る舞いをモダンな形で連携できる点にあります。例えば、HTML に特別な属性(ディレクティブ)を追加することで、クリックイベントの処理や、状態に応じた表示更新などをシンプルに記述できます。

詳細な情報は以下で確認できます(Interactivity API ドキュメント):

ディレクティブとは?

ディレクティブとは、data-wp-* で始まる特殊な HTML 属性(カスタムデータ属性)で、DOM 要素に対して動作や状態を指定するために使います。これらは、JavaScript の処理や状態の制御を、HTML に直接記述する形で実現するための仕組みです。

例えば data-wp-bind や data-wp-on などのディレクティブを HTML に追加することで、ユーザーの操作に応じた表示の更新やイベント処理などを簡単に実装できます。

このようなディレクティブは、WordPress の render.php で出力されるサーバーサイドの HTML に組み込むことができます。render.php にディレクティブを追加することで、サーバーサイドで生成された HTML と、クライアントサイドで動作する Interactivity API の JavaScript が自然に連携します。これにより、状態管理やユーザー操作への反応を、シンプルな HTML 属性だけで記述できるようになります。

以下のように data-wp-on--click ディレクティブを使えば、ボタンがクリックされたときに actions.increment という関数が実行されます(クリックイベントのリスナーを設定できます)。

<button data-wp-on--click="actions.increment">Toggle</button>

また、以下のように状態の表示にも使えます。これは context.count という状態の値を、そのままテキストとして表示する例です。

<span data-wp-text="context.count"></span>

このように HTML にディレクティブを追加することで、以下のようなことが簡単に実現できます。

  • DOM の操作(テキストや属性の変更)
  • CSS の変更(クラスやインラインスタイルの動的適用)
  • ユーザーイベントの処理(クリック、入力、ホバーなど)
  • 状態の表示・更新(カウント表示やトグルの状態など)
代表的なディレクティブ
ディレクティブ 概要
wp-interactive

指定した要素とその子要素を、Interactivity API の対象として有効化します。詳細

wp-context

その要素配下で共有できるローカル状態(context)を定義します。詳細

wp-on

HTML 要素にイベントリスナー(クリックなど)を追加します。詳細

wp-text

HTML 要素のテキストノードを、状態(state)やコンテキストに応じて設定・更新します。 詳細

wp-bind

HTML 属性を、状態やコンテキストの値に基づいて動的に設定・更新します。 詳細

wp-class

状態やコンテキストに応じて、クラス属性を動的に追加・削除します。 詳細

wp-style

状態やコンテキストに応じて、インラインスタイルを動的に設定・削除します。 詳細

wp-watch

要素が生成されたときや、関連する state や context が更新されたときに、指定のコールバック関数を実行します。 詳細

使用する際は data-wp-* の形式で指定します。例:data-wp-interactive="create-block"

公開されているディレクティブのリスト

ストアとは?

Interactivity API では、状態や処理のロジックを ストア(store)という単位で管理します。

ストアでは、アクション(actions)を定義することができ、これはクリックや入力などのユーザーインタラクションに応じて実行されます。アクションは、グローバル状態またはローカルコンテキストを更新し、その変更によって関連付けられた HTML 要素が自動的に再レンダリングされます。

また、アクションとは別に、コールバック(callbacks)も定義できます。コールバックも関数ですが、ユーザーによって直接実行されるのではなく、状態の変化がトリガーとなって実行される点が特徴です。副作用(例:データの取得やログ出力など)を扱いたい場合に使用します。

ストアには以下の要素が含まれます:

  • state(ステート)

    グローバルなリアクティブ変数で、store() 関数の state プロパティで定義します。ページ全体の HTML ノードから参照できます。state のことを「グローバルステート」とも呼びます。

  • context(コンテキスト)

    ローカルな状態や情報で、HTML 側で data-wp-context 属性として定義します。その要素と子要素からアクセス可能で、getContext() 関数を使って取得します。context のことを「ローカルコンテキスト」とも呼びます。

  • actions(アクション)

    通常の JavaScript 関数で、ユーザー操作(クリックや入力など)に応じて data-wp-on ディレクティブなどから呼び出されます。アクションはグローバル状態やローカルコンテキストを更新し、その結果、関連する HTML が自動的に再レンダリングされます。
  • effects(副作用)

    ステートやコンテキストの変化に応じて自動的に実行される処理です。いわゆる「コールバック関数」のように、状態の変化をきっかけに動作します。data-wp-watch や data-wp-init ディレクティブによってトリガーされます。たとえばデータの取得やログ出力など、状態更新以外の副次的な処理を扱います。

ストアは JavaScript ファイル(view.js)内で定義され、例えば、次のように記述します。

// API が提供する関数(store と getContext)を読み込む
import { store, getContext } from "@wordpress/interactivity";

store("my-block", {
  actions: {
    increment() {
      const context = getContext();
      context.count++;
    },
  },
});

ストアは通常、各ブロックに対応する view.js ファイルの中で作成されますが、ブロックの render.php 側からも初期状態(データ)を設定することが可能です。これにより、サーバーサイドで用意したデータをクライアント側の状態としてシームレスに受け渡すことができ、より柔軟なインタラクションの実装が可能になります。

インタラクティブ機能を追加する手順

Interactivity API を使ってインタラクティブなブロックを作るには、以下のような手順を踏みます。

  1. create-block コマンドでブロックのひな型を作成する

    @wordpress/create-block パッケージを使ってブロック開発のベースを作成します。

  2. render.php に HTML + ディレクティブを記述する

    ブロックのフロントエンドマークアップ内に data-wp-* 属性(ディレクティブ)を埋め込み、表示やイベント処理を指定します。

  3. view.js にストア(state や actions、callbacks など)を定義する

    @wordpress/interactivity を使ってストアを定義し、イベント時の動作や状態更新のロジックを記述します。

基本的なインタラクティブブロックの作成

以下では、create-block パッケージとテンプレートを使って簡単なインタラクティブブロックを作成する例を紹介します。

前提条件

このサンプルは WordPress 6.5 以降のバージョンでのみ動作し、Node.js と npm、およびローカル開発環境 (WP ENV や MAMP など) が必要になります。

  • WordPress 6.5 以上
  • Node.js および npm
  • ローカル開発環境

ブロックのひな形の作成

create-block コマンドでインタラクティブブロックのテンプレート(Create Block Interactive Template)を使ってひな形(ブロックの初期構成)を作成します。

参考:クイックスタートガイド

ターミナルでプラグインディレクトリ(wp-content/plugins)に移動して、以下のコマンドを実行します。

npx @wordpress/create-block@latest my-interactive-block --template @wordpress/create-block-interactive-template

上記コマンドにより、以下のようなブロックのディレクトリがプラグインディレクトリに作成されます。

この例では、create-block コマンドで slug に my-interactive-block を指定したので、プラグインのフォルダ名は my-interactive-block(プラグイン名は My Interactive Block)となります。

src ディレクトリを展開すると、開発で使用するファイルを確認できます。

基本的には src ディレクトリ内のファイル(必要に応じてプラグインファイル)を編集します。

build ディレクトリ内のファイルは使用しません(コンパイル時に上書きされます)。

開発プロセスを開始

ひな型が作成されたら、以下のコマンドを実行して、新しく作成したプラグインフォルダーに移動し、npm start で開発プロセスを開始します。

cd my-interactive-block && npm start

開発プロセスを一時的に停止(npm start コマンドを終了)するには control + c を押します。

再開するには再度 npm start を実行します。

プラグインを有効化

作成したブロック My Interactive Block をエディターで使用できる(投稿に挿入できる)ようにするため、管理画面でプラグインを有効化しておきます。

ブロックを挿入

作成したブロック(My Interactive Block)を任意の投稿に挿入して保存します。

エディター側には、edit.js で設定されているメッセージ(p 要素)が表示されます。

// create-block コマンドでブロックを生成した際に自動的に追加されているコード(コメント部分は省略)

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';

export default function Edit( { attributes, setAttributes } ) {
  const blockProps = useBlockProps();

  return (
    <p { ...blockProps }>
      { __(
        'My Interactive Block – hello from the editor!',
        'my-interactive-block'
      ) }
    </p>
  );
}

フロント側を確認すると、render.php に記述されているマークアップにより、以下のように2つのボタンを含むブロックが表示されます。

<?php
// create-block コマンドでブロックを生成した際に自動的に追加されているコード(コメント部分は省略)

$unique_id = wp_unique_id( 'p-' );

wp_interactivity_state(
  'create-block',
  array(
    'isDark'    => false,
    'darkText'  => esc_html__( 'Switch to Light', 'my-interactive-block' ),
    'lightText' => esc_html__( 'Switch to Dark', 'my-interactive-block' ),
    'themeText'	=> esc_html__( 'Switch to Dark', 'my-interactive-block' ),
  )
);
?>

<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"
  data-wp-class--dark-theme="state.isDark"
>
  <button
    data-wp-on--click="actions.toggleTheme"
    data-wp-text="state.themeText"
  ></button>

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

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

Toggle ボタンをクリックするとメッセージの表示・非表示が切り替わり、Switch to dark ボタンをクリックするとブロックの背景色が切り替わります。

これらの表示やインタラクティブな動作は、ひな型を作成した際に、デフォルトで render.php や view.js に記述されているコードによるものです。

// create-block コマンドでブロックを生成した際に自動的に追加されているコード(コメント部分は省略)
import { store, getContext } from '@wordpress/interactivity';

const { state } = store( 'create-block', {
  state: {
    get themeText() {
      return state.isDark ? state.darkText : state.lightText;
    },
  },
  actions: {
    toggleOpen() {
      const context = getContext();
      context.isOpen = ! context.isOpen;
    },
    toggleTheme() {
      state.isDark = ! state.isDark;
    },
  },
  callbacks: {
    logIsOpen: () => {
      const { isOpen } = getContext();
      console.log( `Is open: ${ isOpen }` );
    },
  },
} );

これらのファイルを書き換えることで、簡単に独自のインタラクティブなブロックを作成できます。

Interactivity API 特有の設定

独自ブロックを作成する前に、block.json に含まれる Interactivity API 用の設定を確認しておきます。

これらの設定は、create-block コマンドでブロックを生成した時点で自動的に追加されているので、ここでは内容を把握することが目的です。

src/block.json を開くと以下のような Interactivity API を利用するための必要な設定があらかじめ構成されています。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/my-first-interactive-block",
  "version": "0.1.0",
  "title": "My First Interactive Block",
  "category": "widgets",
  "icon": "media-interactive",
  "description": "An interactive block with the Interactivity API.",
  "example": {},
  "supports": {
    "interactivity": true
  },
  "textdomain": "my-first-interactive-block",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "render": "file:./render.php",
  "viewScriptModule": "file:./view.js"
}

supports.interactivity

"supports": {
  "interactivity": true
},

supports セクションで、interactivity が true に設定されています。この設定により、ブロック内で Interactivity API の使用が有効になります。

render プロパティ

"render": "file:./render.php",

この設定は、ブロックの表示内容を render.php でサーバーサイドから動的に生成することを意味します。つまり作成されたこのブロックはダイナミックブロックとして定義されています。

Interactivity API を使う場合、必ずしもダイナミックブロックである必要はありませんが、サーバーから状態やコンテキストを渡すケースが多いため、ダイナミックブロックとして作成されることが一般的です。

render.php から data-wp-* 属性(ディレクティブ)を出力することで、JavaScript 側と連携できます。

viewScriptModule プロパティ

"viewScriptModule": "file:./view.js"

このプロパティは、フロントエンドで読み込まれる JavaScript モジュール(ESM形式) を指定するものです。view.js ファイル内では、インタラクティブな挙動(状態やアクション、コールバック)を定義します。

Interactivity API は ESModules ベースの構成を前提としているため、従来の viewScript ではなく、viewScriptModule を使用する必要があります(viewScriptModule)。

view.js には通常、store の登録(状態・アクション)などを記述します。

シンプルなカウンターブロックの例

@wordpress/create-block コマンドで生成されたひな型の render.php と view.js を活用し、シンプルなカウンターブロックを作成します。

render.php

render.php は、ブロックのフロントエンドで表示される HTML を生成するファイルです。

既存のコードは削除して、以下のような「Count Up」ボタンの <button> 要素とカウントを表示する <p> 要素を含むマークアップを記述します。

ブロックの外側(ラッパー要素)の <div> 要素には、get_block_wrapper_attributes() を使って標準のラッパー属性を追加します(ダイナミックレンダーマークアップ)。

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context( array( 'count' => 0 ) ); ?>
>
  <p>Count: <span data-wp-text="context.count"></span></p>
  <button data-wp-on--click="actions.increment">Count Up</button>
</div>

使用しているディレクティブ

  • data-wp-interactive="create-block"
    • このディレクティブを使うことで、Interactivity API にこの要素を対象として認識させます。
    • 値(ここでは "create-block")は、view.js 側の store() 関数で指定する名前空間と一致させる必要があります。
  • wp_interactivity_data_wp_context( array( 'count' => 0 ) )
    • data-wp-context 属性(ディレクティブ)を PHP 関数で生成する書き方です。
    • array( 'count' => 0 ) により、カウント値を 0 に初期化したローカルコンテキスト(状態)を定義します。
    • 手動で data-wp-context='{"count":0}' と書くのと同じ意味ですが、PHP 関数の方が推奨されます。
  • <span data-wp-text="context.count">
    • span 要素のテキストに context.count の値をリアクティブに表示します。
    • カウント値が更新されると、自動的にこの部分の表示も更新されます。
  • <button data-wp-on--click="actions.increment">
    • クリックイベントに反応して、ストアに定義した increment アクションを実行します。
    • イベントリスナーは data-wp-on--[イベント名] 形式(ディレクティブ)で指定します。

このように、HTML にディレクティブ(data-wp- 属性)を記述するだけで、JavaScript の状態や動作と簡単に連携できます(各ディレクティブの詳細)。

次に view.js でアクションの処理を定義します。

view.js

view.js では、ブロックにインタラクティブな動作を追加するための ストア(store) を定義します。

// Interactivity API が提供する関数(store と getContext)の読み込み
import { store, getContext } from "@wordpress/interactivity";

// count の値を 1 増やすアクションを持つストアを定義
store("create-block", {
  actions: {
    increment() {
      const context = getContext();
      context.count ++;
    },
  },
});

使用する関数の読み込み

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

  • store():ストア(状態・アクションなどのロジックの集合)を定義するための関数です。
  • getContext():現在の HTML ノードのコンテキスト(data-wp-context で定義された状態)を取得するための関数です。これを使うことで、render.php から注入された(初期)状態にアクセスできます。

ストアの定義

上記では、count の値を 1 増やすアクションを持つストアを定義しています。

  • store("create-block", {...})
    • ストアに "create-block" という名前(名前空間)を付けています。
    • この名前は render.php の data-wp-interactive="create-block" の値と一致させる必要があります。
  • actions.increment()
    • data-wp-on--click="actions.increment" から呼び出されるアクション関数です。
    • getContext() を使って現在のカウント値(context.count)を取得し、値を 1 増やしています。

context.count++ のように ローカルコンテキスト(context)を直接変更することで、対応する表示(例:<span data-wp-text="context.count">)も自動で更新されます。

Interactivity API では、状態(context や state)の変更と表示更新の自動連携が特徴です。

これで render.php で定義したボタンをクリックすると、カウントが増加してリアルタイムに表示が更新されるようになります。

フロントエンドを確認すると、以下のようにカウント(初期状態は0)と「Count Up」ボタンが表示され、ボタンをクリックするとカウントが増加していきます。

ディレクティブ

以下は使用した各ディレクティブについての少し詳しい説明です。

wp-interactive ディレクティブ

wp-interactive ディレクティブは、Interactivity API による状態管理やイベント処理を HTML 要素に関連付けるためのディレクティブです。主に <div> 要素に指定し、その要素と子要素を対象に、インタラクティブな機能を有効化します。

この属性(data-wp-interactive)にはストアの名前空間(namespace) を指定します。

例えば、以下のように記述します。

<div data-wp-interactive="create-block">

ディレクティブに指定する値("create-block")は、view.js ファイル内で定義する store() 関数の第1引数と一致させる必要があります。

store("create-block", {
  // actions や state など
});

この対応により、DOM 要素と JavaScript 側の状態管理(ストア)がリンクされ、ユーザー操作に応じた動作が実現されます。

将来的な動向

data-wp-interactive は、将来的に WordPress コアによって自動で付与される予定ですが、現時点では手動で指定する必要があります。

実際の使用例

前述の render.php では、最上位の <div> 要素(ラッパー)にこのディレクティブが指定されています。

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context( array( 'count' => 0 ) ); ?>
>
  ...
</div>

この <div> では、以下の属性やディレクティブを組み合わせて使用しています:

  • get_block_wrapper_attributes():WordPress 標準のブロックラッパー属性を出力
  • data-wp-interactive="create-block":このブロックに Interactivity API の機能を適用し、create-block ストアと関連付け(wp-interactive ディレクティブ)
  • wp_interactivity_data_wp_context():data-wp-context を出力し、初期状態を定義(この例では count: 0)
wp-context ディレクティブ

wp-context ディレクティブは、要素とその子要素内で有効なローカル状態(context)を定義するためのディレクティブです。たとえば、カウンターブロックのように「Count Up」ボタンのクリックに応じてカウント値を保持する場合、この context に値を保存して管理します。

特徴

  • ローカルスコープ

    context に定義された値は、指定した要素とその子孫要素の範囲内でのみ参照可能です。他のブロックやコンポーネントからはアクセスできません。

  • JSON 形式で定義

    context は JSON データで定義します(PHP 側では配列を使用)。

安全な出力には wp_interactivity_data_wp_context() を使う

WordPress では、data-wp-context を安全かつ正確に出力するためのユーティリティ関数 wp_interactivity_data_wp_context() が用意されています。

この関数は、PHP の配列を受け取り、自動的にエスケープ処理された JSON を data-wp-context 属性として返します(出力するには echo が必要です)。

以下は、context の count を 0 に初期化する例です。

wp_interactivity_data_wp_context() は戻り値を返すだけなので、echo しないと HTML に data-wp-context="..." が出力されないので注意が必要です。

<div <?php echo wp_interactivity_data_wp_context( array( 'count' => 0 ) ); ?> >

JavaScript 側(view.js)では、getContext() 関数を使ってこの context を取得し、状態の取得・更新が可能です。

直接記述も可能

data-wp-context は、以下のように値に JSON を直接記述することもできます。

<div data-wp-context='{ "count": 0 }'>

但し、手動記述は構文ミスやエスケープ漏れの原因になりやすいため、WordPress 公式では wp_interactivity_data_wp_context() の使用が推奨されています。

wp-on ディレクティブ

ユーザー操作(クリックや入力など)に応じた処理を行うには、対象の要素にイベントリスナーを追加する必要があります。

Interactivity API では、DOM イベントに対して簡潔にイベントリスナーを設定できるよう、wp-on ディレクティブを提供しています。

このディレクティブは、以下の構文で使用します。イベント名はハイフン2つ(--)で区切られます。

data-wp-on--[イベント名]="アクション名"

例えば、クリックイベントに反応させるには data-wp-on--click を使い、次のように記述します。

<button data-wp-on--click="actions.increment">Count Up</button>

wp-on ディレクティブは、関連するイベントがトリガーされるたびに実行されます。

この例では、ボタンがクリックされたときに、ストア(view.js 内)で定義された increment アクション(actions.increment)が実行されます。

ポイント

  • wp-on は、JavaScript の addEventListener() と同様に、DOM イベントを HTML 属性で簡単にバインドできる仕組みです。
  • "actions.〇〇" の形式で、ストアに定義されたアクション関数を指定します。
  • イベント名(例:click, input, mouseover など)は、基本的に標準の DOM イベントと同じです。
  • アクション関数は、第1引数としてイベントオブジェクト(event)を受け取ることができます。
  • アクション関数の返り値は無視されるため、何か値を返しても、それが UI や他の処理に影響を与えることはありません。
actions: {
  handleClick( e ) {  // 必要に応じて引数にイベントオブジェクトを受け取ることができる
    e.preventDefault(); // イベントオブジェクトを使って処理を制御
    console.log("クリックされました");
    return "これは無視されます"; // この返り値は利用されません
  }
}
wp-text ディレクティブ

wp-text ディレクティブは、HTML 要素の 内部テキスト(テキストノード) を動的に設定・更新するための Interactivity API のディレクティブです。

このディレクティブに指定した値は、リアクティブに評価され、その戻り値が文字列として解釈され、対象要素の中身(inner text)として反映されます。

ステート(state)やコンテキスト(context)の値が変更されると、対応する要素のテキストも自動的に更新されます。

使用例

<div data-wp-text="context.message"></div>

上記のように記述すると、context.message の値が "Hello" の場合、ブラウザ上では次のように表示されます(実際には data-wp-text 属性は要素にそのまま残ります)。

<div>Hello</div>

つまり、context.message の値が動的に DOM に反映される形になります。

実行タイミング

wp-text は、以下のタイミングで評価・反映されます。

  • 要素が DOM に作成(マウント)されたとき
  • state または context 内の、関連するプロパティが変更されたとき(式やコールバック関数の評価結果が変化したとき)

補足

  • 双方向バインディングではありません(HTMLから値を入力する用途には使いません)。
  • data-wp-text の値は文字列として評価されます。数値や式なども使用可能ですが、返り値はテキストとして扱われます。

シンプルなトグルブロックの例

以下で作成するブロックは、create-block コマンドで作成したブロックのひな型に初期状態で記述されているサンプルとほぼ同じもになります。

まず、ボタンをクリックするとテキストの表示・非表示を切り替える「トグル機能付きブロック」を作成します。その後、背景色を切り替えるボタンの機能を追加します。

前回のカウンターブロックと同様、create-block コマンドで生成されたひな型の render.php と view.js を利用します(コードは一度すべて削除してから作り直します)。

render.php

render.php を以下のように書き換えます。

この render.php では、ブロックのラッパー要素の div 要素、その子要素の「Toggle」ボタンとそれに応じて表示・非表示が切り替わる <p> 要素を含むマークアップを記述しています。

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ); ?>
>
  <button data-wp-on--click="actions.toggle">Toggle</button>
  <p data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
</div>

各ディレクティブの説明

  • data-wp-interactive="create-block"

    このディレクティブを付与することで、この要素とその子要素に Interactivity API のインタラクティブな動作が有効になります。

  • <?php echo wp_interactivity_data_wp_context( array( 'isOpen' => false ) ); ?>

    data-wp-context 属性を安全に出力するための関数です。初期状態として isOpen: false を定義し、context.isOpen でアクセスできるようにします。

  • <button data-wp-on--click="actions.toggle">

    ボタンクリック時に view.js 側で定義した toggle() アクションを呼び出します。これにより isOpen の状態が反転し、<p>要素に指定した wp-bind ディレクティブにより表示切り替えが行われます。

  • <p data-wp-bind--hidden="!context.isOpen">

    isOpen の状態に応じて、この段落を表示または非表示にします。true のとき表示され、false のとき hidden 属性が付与されて非表示になります。

view.js

このファイルでは、ブロックにインタラクティブな動作を追加するために、Interactivity API の store() を使って ストア(状態と処理の定義) を作成します。

このブロックでの動作の流れ

  • ユーザーが「Toggle」ボタンをクリックすると、toggle() アクションが実行されます。
  • アクションの中では、getContext() を使って現在の context を取得し、その中の isOpen の値を反転(true ⇔ false)します。
  • isOpen の値が更新されると、render.php 側の <p> 要素に設定された data-wp-bind--hidden="!context.isOpen" が再評価され、要素が表示または非表示に切り替わります。
// Interactivity API が提供する関数をインポート
import { store, getContext } from "@wordpress/interactivity";

// ストア(状態と処理の定義) を作成
store("create-block", {
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen; // 値を反転
    },
  },
});

toggle() アクション

getContext() で現在のコンテキストを取得し、context.isOpen を true/false で切り替えています。

処理はこれだけで、Interactivity API が変更を検知し、関連する DOM(この場合は <p>)を自動で更新します。

フロントエンドを確認すると、以下のような Toggle ボタンが表示されます。

Toggle ボタンをクリックすると、p 要素のテキストが表示され、再度ボタンをクリックするとテキストが非表示になります。

wp-bind ディレクティブ

wp-bind ディレクティブは、HTML 要素の属性を状態(state)やコンテキスト(context)の値に基づいて動的に設定・更新するための Interactivity API の機能です。

構文は以下のようになります。

data-wp-bind--[属性名]="ストアのプロパティへの参照"

このディレクティブを使うと、JavaScript 側の値に応じて、属性の追加・削除・更新を自動的に行うことができます。

HTML 属性をリアクティブに制御できるので、hidden や disabled、checked、aria-* などの属性と組み合わせることで、ユーザー操作に応じた状態切り替えを簡潔に記述できます。

使用例:属性の ON/OFF 切り替え

次の例では、context.isOpen の値に応じて <p> 要素の hidden 属性を動的に制御します。

<p data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
  • context.isOpen が false のとき(!context.isOpen は true)
    → hidden 属性が追加されて、 <p> 要素は非表示になります。
  • context.isOpen が true のとき(!context.isOpen は false)
    → hidden 属性が削除されて、 <p> 要素が表示されます。

実行タイミング

wp-bind ディレクティブは、以下のタイミングで評価・適用されます。

  • 要素が初めて DOM に追加されたとき
  • 対象となる state や context の値が変更されたとき

これにより、インタラクティブな操作に応じて DOM の状態が常に最新に保たれます。

動作ルール(値による振る舞いの違い)

wp-bind のバインド対象がどのように属性として扱われるかは、値の型に応じて以下のように決まります。

値の種類 DOM 出力の例 説明
true <div hidden> 属性が追加される(値なし)
false <div> 属性が削除される
"文字列" <div hidden="文字列"> 属性が追加され、文字列値が代入される
true(aria-*, data-*) <div aria-expanded="true"> 属性名が aria- または data- で始まり、値が真偽値 (true または false) の場合、真偽値も文字列化されて属性に反映される

aria- 属性を使う例

前述の render.php を以下のように書き換えて、aria- 属性を活用してアクセシビリティ(特にスクリーンリーダーへの対応)を高める例です。

<?php
// ユニークな ID を自動生成(この ID を、ボタンが制御する p 要素に id 属性として付与)
$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)); ?>>

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

  <p
    id="<?php echo esc_attr($unique_id); ?>"
    data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
</div>
  • $unique_id = wp_unique_id('p-');
    • wp_unique_id() 関数を使って、ユニークな ID(例: p-123)を自動生成しています。
    • この ID は、ボタンが制御する要素(<p>タグ)に id 属性として付けられます。
  • aria-controls="<?php echo esc_attr($unique_id); (ボタン要素に追加)
    • この属性により、ボタンが「どの要素を制御しているか」を明示的に示しています。
    • スクリーンリーダーなどの支援技術に対して、ボタンが <p> 要素の表示状態を切り替える役割であることを伝えるために使います。
  • data-wp-bind--aria-expanded="context.isOpen"(ボタン要素に追加)
    • この wp-bind ディレクティブは、context.isOpen の値に応じて aria-expanded 属性を自動的にボタン要素に設定します。
    • isOpen が true のとき:aria-expanded="true"
    • isOpen が false のとき:aria-expanded="false"
    • これにより、「このボタンを押すと開閉がある」ことを支援技術が正しく認識できます。
  • <p id="<?php echo esc_attr($unique_id); ?>" ...>
    • 先ほど生成した一意の ID を、この <p> 要素に設定します。
    • これで、ボタンの aria-controls と関連づけが成立します。

例えば、フロントエンドのマークアップは以下のような構造になります(段落が非表示状態の場合の例)。

<div
  class="wp-block-create-block-my-interactive-block"
  data-wp-interactive="create-block"
  data-wp-context="{'isOpen':false}">

  <button
    aria-expanded="false"
    data-wp-on--click="actions.toggle"
    data-wp-bind--aria-expanded="context.isOpen"
    aria-controls="p-2">
    Toggle
  </button>

  <p id="p-2" data-wp-bind--hidden="!context.isOpen" hidden>Hello from my interactive block!</p>
</div>
  • button 要素には aria-controls="p-2" が指定され、aria-expanded="false" が追加される
  • p 要素に id="p-2" が指定され、非表示状態なので hidden 属性が追加される
wp-class ディレクティブ

wp-class ディレクティブは、真偽値の評価に応じて HTML 要素にクラス属性を動的に追加・削除するための仕組みです。

構文

data-wp-class--[クラス名]="ストアのプロパティへの参照"
  • クラス名:要素に追加または削除される CSS クラス名
  • ストアのプロパティへの参照:true ならクラスを追加、false ならクラスを削除

クラス名は「ケバブケース」を使用

HTML 属性は大文字・小文字を区別しないため、wp-class のディレクティブで指定するクラス名には、キャメルケース(例: isDark)ではなくケバブケース(例: is-dark)を使う必要があります。

<!-- 正しい指定(ケバブケース .is-dark クラス) -->
<p data-wp-class--is-dark="context.isDark">Text</p>

以下はよくある間違いです。

<!-- よくある間違い(キャメルケースは NG)-->
<p data-wp-class--isDark="context.isDark">Text</p> 

使用例:条件に応じて .hidden クラスを切り替える

以下の例では、context.isOpen が false(!context.isOpen が true)のときだけ .hidden クラスを追加し、<p> 要素を非表示にします。

<p data-wp-class--hidden="!context.isOpen">Hello from my interactive block!</p>
  • isOpen === false → クラス hidden が付与される(非表示)
  • isOpen === true → クラスが削除される(表示)

スタイル定義(style.scss など)

上記の .hidden クラスは、以下のように CSS で定義しておきます。

.hidden {
  display: none
}

複数クラスの制御について

必要に応じて、複数の wp-class ディレクティブを同じ要素に記述することも可能です。

各クラスの付け外しはそれぞれの式で独立に評価されます。

<div
  data-wp-class--is-open="context.isOpen"
  data-wp-class--has-error="context.hasError"
>
wp-style ディレクティブ

wp-style ディレクティブは、状態やコンテキストの値に応じて、HTML 要素にインラインスタイルを動的に設定または削除するための機能です。

構文

data-wp-style--[CSSプロパティ名]="ストアのプロパティへの参照"
  • [CSSプロパティ名]:スタイルとして設定したいプロパティ(例: color, background-color など)
  • ストアのプロパティへの参照:context や state の値

動作ルール

wp-style による属性の追加・削除は、評価された値によって以下のように変化します。

値の型・内容 動作内容
false または 0 スタイル属性は削除される
文字列 属性が追加され、指定された値が割り当てられる(例: "red" や "20px")
その他(undefined など) スタイルは適用されない

使用例

以下のコードでは、context.color の値がインラインの color スタイルとして適用されます。

<p data-wp-style--color="context.color">Hello World!</p>
  • context.color = "red" → <p style="color: red">Hello World!</p>
  • context.color = false または 0 → <p>Hello World!</p>(スタイルなし)

以下のように、複数のスタイルを同時に動的制御することも可能です。

<div
  data-wp-style--background-color="context.bgColor"
  data-wp-style--font-size="context.fontSize"
>
  Dynamic style block
</div>

スタイルに数値を使用する際、単位(px, em, % など)を文字列で明示的に含める必要があります。

トグルブロックを wp-style で切り替える(推奨されない例)

先のトグルブロックを wp-style を使ってテキストの表示・非表示を切り替える例です。

context.isOpen は真偽値であり、wp-style--display のような CSS プロパティには "block" や "none" のような文字列が必要です。

そのため、以下の例では isOpen を直接使うのではなく、切り替え用の文字列値 display を context に追加し、アクション内で isOpen の状態に応じて "block" または "none" を代入します。但し、この方法は状態管理が冗長になるなどの欠点があります。

<?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, 'display' => 'none' ) ); ?>
>
  <button
    data-wp-on--click="actions.toggle"
    data-wp-bind--aria-expanded="context.isOpen"
    aria-controls="<?php echo esc_attr($unique_id); ?>"
  >
  Toggle
  </button>

  <p
    id="<?php echo esc_attr($unique_id); ?>"
    data-wp-style--display="context.display"
  >
  Hello from my interactive block!
  </p>
</div>

view.js のストアで context.isOpen の値を利用して context.display の値を設定します。

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

store("create-block", {
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
      // context.isOpen の値を利用して context.display の値を設定
      context.display =  context.isOpen ? 'block' : 'none';
    },
  },
});

トグルブロックを wp-style で切り替える(より合理的な方法)

context.isOpen の状態に応じて、要素の表示・非表示を切り替えるには、wp-style ディレクティブを使って style 属性を動的に制御することができます。

但し、前述の例のように、表示用の状態(例:display)を context に明示的に保持すると、context.isOpen との整合性を常に保つ必要があり、状態管理が冗長になります。

代わりに、state.display に getter 関数を定義することで、context.isOpen に基づいた派生ステートとして "block" または "none" を返すようにできます。

この方法では、アクション関数は context.isOpen の制御に専念でき、表示制御のロジックは state.display の中に集約され、コードがよりシンプルで保守しやすくなります。

参考: 派生ステートを使用しない例と使用する例

派生ステートを使用

Interactivity API の store() に渡すオブジェクトには、state プロパティを定義できます。

ここに getter 関数を含めることで、context の状態に依存した派生ステート(derived state)を計算できます(派生ステート)。

以下は context.isOpen の状態に応じて、"none" または "block" を返す display プロパティを定義する例です。

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

store("create-block", {
  state: {
    // 派生状態(derived state)としての display プロパティ( getter 関数 )を定義
    get display() {
      const context = getContext();
      return context.isOpen ? "block" : "none";
    },
  },
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
  },
});

通常、state を使用する場合はストアから分割代入で取得しますが、この例の state.display のような getter 関数 は context(コンテキスト)を引数として受け取り、その状態に応じた値を返すため、ストアの内部から state を直接取得する必要はありません。そのため、ストアの定義内では state を分割代入しなくても getter は機能します。

<?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 ) ); ?>
>
  <button
    data-wp-on--click="actions.toggle"
    data-wp-bind--aria-expanded="context.isOpen"
    aria-controls="<?php echo esc_attr($unique_id); ?>"
  >
  Toggle
  </button>

  <p
    id="<?php echo esc_attr($unique_id); ?>"
    data-wp-style--display="state.display"
  >
  Hello from my interactive block!
  </p>
</div>

state.display は、context.isOpen をもとに計算される「派生状態」です。

data-wp-style--display="state.display" により、表示状態が自動的に "block" または "none" に切り替わります。

インラインスタイルとして <p style="display: none">...</p> のように反映されます。

アクセシビリティの観点では display: none に注意

display: none は要素を完全に非表示にするため、スクリーンリーダーなどの支援技術にも見えなくなります。アクセシビリティ重視なら、data-wp-bind--hidden="!context.isOpen" の方がより適しているケースもあります。

state を使う

以下では、トグルブロックに「背景色を切り替えるボタン」を追加します。

  • 初期状態ではボタンのテキストは「Switch to Dark」。
  • ボタンをクリックすると背景色がダークに切り替わり、テキストは「Switch to Light」になります。

ページ上に複数のブロックがある場合でも、すべてのブロックが同じ背景状態を共有するように、背景色の状態は state(グローバルステート)で管理します。

グローバルステートの概要

Interactivity API の state プロパティは、ページ全体で共有されるグローバルステートです。

  • 通常、PHP 側で wp_interactivity_state() を使って初期値を設定します。
  • JavaScript 側でも定義できますが、この例では PHP 側で行っています。

render.php

render.php を以下のように書き換えます。

state の初期値を設定する wp_interactivity_state() の第1引数には名前空間の文字列を指定し、第2引数は値を含む連想配列を指定します。この例では以下のような初期値を設定しています。

  • isDark:背景色がダークカラーかどうか
  • darkText:背景色がダークカラーの場合のボタンのテキスト
  • lightText:背景色がライトカラーの場合のボタンのテキスト
  • themeText:現在の(背景色に対応した)ボタンのテキスト

themeText は JavaScript 側(view.js)で getter により動的に定義されますが、PHP 側でも初期状態として themeText を明示的に設定しておくことで、JavaScript が読み込まれる前の初期描画時に適切なテキストが表示されます。アクセシビリティや FOUC(Flash of Unstyled Content)防止にも有効です。

マークアップでは、ラッパーの div 要素に wp-class ディレクティブを指定して、state.isDark が true の場合は dark-theme クラスを出力されます(このクラスを使って背景色を切り替えます)。

背景色を切り替えるボタン要素には、data-wp-on--click を指定して、ボタンがクリックされたら actions.toggleTheme アクション関数を呼び出し、ボタンのテキストは data-wp-text を使って、state.themeText の値を出力します。

<?php
// state の初期値を設定
wp_interactivity_state(
  'create-block',
  array(
    'isDark'    => false,
    'darkText'  => 'Switch to Light',
    'lightText' => 'Switch to Dark',
    'themeText'  => 'Switch to Dark',
  )
);
?>

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context(array('isOpen' => false)); ?>
  data-wp-class--dark-theme="state.isDark"
>

  <button
    data-wp-on--click="actions.toggleTheme"
    data-wp-text="state.themeText"
  ></button>

  <button data-wp-on--click="actions.toggle">Toggle</button>
  <p data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
</div>
  • data-wp-class--dark-theme="state.isDark" によって、.dark-theme クラスが制御される。
  • data-wp-text="state.themeText" によって、ボタンに現在のテキスト(ラベル)が表示される。

view.js

view.js を以下のように書き換えます。

この view.js では、背景色とボタンのテキストを切り替えるために、store() を使ってグローバルステートとアクションを定義しています。

まず、グローバルステート state をストアから分割代入で取得します。

次に、state.isDark の状態に応じてボタンのテキストを動的に変更するため、themeText という 派生ステート を getter を使って定義します。これにより、状態に応じた値をリアクティブに取得でき、ボタンのラベル(テキスト)を切り替えることができます。

背景色の切り替え処理は toggleTheme() アクション関数として定義しており、ボタンがクリックされると state.isDark の値を反転し、div 要素に指定した data-wp-class--dark-theme ディレクティブで .dark-theme クラスを追加・削除することで背景色を切り替えます。

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

// ストアから分割代入で state (グローバルステート)を取得
const {state} = store("create-block", {
  state: {
    // 派生ステートを getter を使って定義
    get themeText() {
      // state.isDark の状態に応じてテキストを設定
      return state.isDark ? state.darkText : state.lightText;
    },
  },
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
    // 背景色を切り替えるボタンのアクション関数
    toggleTheme() {
      state.isDark = ! state.isDark; // 値を反転
    },
  },
});

state をストアから分割代入で取得しないといけない理由

themeText のような state の派生プロパティ(getter)で state 自身を参照する場合、store() の戻り値から state を分割代入して取得する必要があります。これは、state.themeText の getter 内部で state を参照するため、ストア定義の外で state を使えるようにしておく必要があるためです。

style.scss

ブロックのラッパー要素(div 要素)の data-wp-class--dark-theme ディレクティブにより、.dark-theme が付与された場合のスタイルを設定します(&.dark-theme によって、ボタンやテキストも含めたスタイル全体を切り替えています)。

以下のスタイルは create-block コマンドでひな型を作成した際に、初期のサンプル用にすでに設定されているものを、そのまま使用しています。

.wp-block-create-block-my-interactive-block {
  font-size: 1em;
  background: #ffff001a;
  padding: 1em;

  &.dark-theme {
    background: #333;
    color: #fff;

    button {
      background: #555;
      color: #fff;
      border: 1px solid #777;
    }

    p {
      color: #ddd;
    }
  }
}
派生ステート(derived state)

派生ステートとは、一般的に、他のステート(状態)に基づいて動的に算出されるステートのことです。直接ユーザー操作などで更新される「ソースステート(元の状態)」とは異なり、他のステートの変化に応じて自動的に更新されるという特徴があります。

WordPress の Interactive API における「派生ステート(derived state)」とは、他のステート(状態)から導き出される状態のことで、以下のような特徴があります。

  • 依存するステートが変更されると自動的に再計算される
  • コンポーネントにおける パフォーマンスの最適化やロジックの分離に有効

派生ステートを getter で定義

JavaScript で派生ステートは getter(取得専用プロパティ)を使用して定義します。

以下のコードでは、themeText という派生ステートを getter を使って定義しています。

state: {
  // 派生ステートを getter を使って定義
  get themeText() {
    // state.isDark の状態に応じてテキストを設定
    return state.isDark ? state.darkText : state.lightText;
  },
},
  • themeText は state.isDark の値に基づいて、どのテキスト(state.darkText または state.lightText)を表示すべきかを返しています。
  • themeText は他のステート(isDark, darkText, lightText)に依存しており、それらが変更されると themeText の結果も自動的に変わります。
  • themeText 自体は計算された読み取り専用の値なので、外部から直接書き換えることはできません。

以下は、コンテキスト(context)の状態に応じて、トグルボタンのテキストを切り替える例です。

toggleText という派生ステート(derived state)を getter を使って定義しています。この派生ステートは context.isOpen の状態に基づいて値を返すため、getContext() を使ってコンテキストを取得しています。

state: {
  // 背景色に応じたボタンテキスト
  get themeText() {
    return state.isDark ? state.darkText : state.lightText;
  },
  // 派生ステート toggleText を getter で定義
  get toggleText() {
    const context = getContext();
    // context.isOpen の状態に応じて表示テキストを切り替え
    return context.isOpen ? "Hide" : "Show";
  },
},
import { store, getContext } from "@wordpress/interactivity";

const {state} = store("create-block", {
  state: {
    // 背景色に応じたボタンテキスト
    get themeText() {
      return state.isDark ? state.darkText : state.lightText;
    },
    // 派生ステート toggleText を getter で定義
    get toggleText() {
      const context = getContext();
      // context.isOpen の状態に応じて表示テキストを切り替え
      return context.isOpen ? "Hide" : "Show";
    },
  },
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
    toggleTheme() {
      state.isDark = ! state.isDark;
    },
  },
});

render.php では、トグルボタンのテキストを、data-wp-text ディレクティブで state.toggleText にバインドします。

<button
  data-wp-on--click="actions.toggle"
  data-wp-text="state.toggleText"
></button>
<?php
wp_interactivity_state(
  'create-block',
  array(
    'isDark'    => false,
    'darkText'  => 'Switch to Light',
    'lightText' => 'Switch to Dark',
    'themeText'  => 'Switch to Dark',
  )
);
?>

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context(array('isOpen' => false)); ?>
  data-wp-class--dark-theme="state.isDark"
>

  <button
    data-wp-on--click="actions.toggleTheme"
    data-wp-text="state.themeText"
  ></button>

  <button
    data-wp-on--click="actions.toggle"
    data-wp-text="state.toggleText"
  ></button>

  <p data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
</div>
wp-watch ディレクティブ

wp-watch ディレクティブは、ノード(ブロックなど)が作成されたり、その state や context が変更された際に、対応するコールバック関数(store の callbacks に定義された関数)を実行するために使用されます。

これらの callbacks は actions(アクション)と同様に関数ですが、主に副作用(たとえば、外部データの取得、ログの出力、DOM 操作など)を扱うために使われます。副作用のない状態管理には actions を使用し、副作用を伴う処理には callbacks を wp-watch 経由でフックするという使い分けが想定されています。

  • wp-watch は状態変化やノード生成にフックする仕組み。
  • callbacks は副作用のある処理向け(例:console.log、API 取得など)。
  • actions は純粋な状態変更向け(副作用なし)。
  • 状態やコンテキストが変化した時点で callbacks を自動実行できる

副作用(side effect)

副作用(side effect)とは、関数や処理が(本来の目的以外に)外部の状態を変更したり、外部からの影響を受けたりすることを指します。

例えば以下のようなものが副作用です。

副作用の例 説明(なぜ副作用か)
DOM の操作 HTML 要素の書き換えなど(ブラウザの外部状態を変更するため)
コンソール出力 console.log() など(コンソールへの出力という外部とのやりとりがあるため)
ネットワークリクエスト fetch() などで API 通信を行うこと(ネットワーク通信が発生し、外部のデータに依存するため)
データの保存 ローカルストレージやサーバーに保存(外部のストレージにアクセスしているため)
タイマー処理 setTimeout() や setInterval() など(時間の経過という外部要因に依存するため)

以下は、ブロックのラッパー要素に data-wp-watch="callbacks.logStateStatus" を指定し、ページ読み込み時や state や context の変更時に、現在の状態をコンソールに出力する例です。

<?php

wp_interactivity_state(
  'create-block',
  array(
    'isDark'    => false,
    'darkText'  => 'Switch to Light',
    'lightText' => 'Switch to Dark',
    'themeText'  => 'Switch to Dark',
  )
);
?>

<!-- data-wp-watch を追加  -->
<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context(array('isOpen' => false)); ?>
  data-wp-class--dark-theme="state.isDark"
  data-wp-watch="callbacks.logStateStatus"
>

  <button
    data-wp-on--click="actions.toggleTheme"
    data-wp-text="state.themeText"
  ></button>

  <button
    data-wp-on--click="actions.toggle"
    data-wp-text="state.toggleText"
  ></button>

  <p data-wp-bind--hidden="!context.isOpen">Hello from my interactive block!</p>
</div>
import { store, getContext } from "@wordpress/interactivity";

const {state} = store("create-block", {
  state: {
    // 背景色に応じたボタンテキスト
    get themeText() {
      return state.isDark ? state.darkText : state.lightText;
    },
    // トグル状態に応じたボタンテキスト
    get toggleText() {
      const context = getContext();
      return context.isOpen ? "Hide" : "Show";
    },
  },
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
    toggleTheme() {
      state.isDark = ! state.isDark;
    },
  },
  // コールバックを定義
  callbacks: {
    logStateStatus: () => {
      const { isOpen } = getContext();
      console.log( `Is Open: ${ isOpen } | Is Dark: ${ state.isDark }` );
    },
  },
});
  • data-wp-watch は、対象要素が DOM に追加された時と、参照している state や context の値が変更された時に、自動的に指定されたコールバック関数を実行します。
  • この例では、isOpen(context)と isDark(state)の状態を監視し、変更があった際に現在の状態をログ出力します。

これで、create-block コマンドで作成したブロックのひな型に初期状態で記述されているサンプルとほぼ同じ機能のブロックになりました(aria-属性などは省略しています)。

テキストやラベルを編集可能に

このブロックでは、ユーザーがトグルボタンで表示するメッセージやボタンのラベルを自由に設定でき、さらに必要に応じてカスタムクラスをラッパー要素に追加できます。

まず、これらのデータを保存するための属性(attributes)を block.json に定義し、edit.js で編集できるようにします。

block.json

追加する属性

  • message — トグルボタンで表示するメッセージ本文
  • showMessageLabel — メッセージ表示時に使うボタンのラベル
  • hideMessageLabel — メッセージ非表示時に使うボタンのラベル
  • customClass — ラッパー要素に追加するクラス名(スペース区切りで複数可)
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/my-interactive-block",
  "version": "0.1.0",
  "title": "My Interactive Block",
  "category": "widgets",
  "icon": "media-interactive",
  "description": "An interactive block with the Interactivity API.",
  "example": {},
  "supports": {
    "interactivity": true
  },
  "attributes": {
    "message": {
      "type": "string",
      "default": "Hello from Toggle Box!"
    },
    "showMessageLabel": {
      "type": "string",
      "default": "Show Message"
    },
    "hideMessageLabel": {
      "type": "string",
      "default": "Hide Message"
    },
    "customClass": {
      "type": "string",
      "default": ""
    }
  },
  "textdomain": "my-interactive-block",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "render": "file:./render.php",
  "viewScriptModule": "file:./view.js"
}
edit.js

このブロックでは、インスペクターパネルから表示・非表示用ボタンのラベルやラッパー要素のカスタムクラスを編集でき、さらに本文メッセージの入力には RichText コンポーネントを使用して装飾可能にしています。

customClass 属性についてはスペース区切りで複数のクラス名を指定できるようにしていますが、入力された値が CSS のクラス名として無効な文字列を含まないようにバリデーションを行っています。これにより、不正なクラス名による表示崩れやセキュリティリスクを防止します。

主な実装ポイント

  1. InspectorControls と TextControl
    • サイドバーの設定パネル(InspectorControls)内に PanelBody を配置し、showMessageLabel と hideMessageLabel を入力するための TextControl を用意。
    • customClass の入力欄も用意し、スペース区切りで複数クラスが指定できる旨をヘルプテキストで明記。
  2. RichText によるメッセージ編集
    • 本文のメッセージは RichText コンポーネントで入力。
    • 太字、斜体、リンクなどの装飾を可能にするため、後続の render.php では wp_kses_post() などで安全に許可するタグを制御します。
  3. className の出力条件
    • useBlockProps() に className: customClass || undefined を渡すことで、customClass が空文字の時に不要な class="" が出力されないようにしています。
    • これにより、無駄な空属性が HTML に含まれず、クリーンなマークアップが保たれます。
  4. カスタムクラスのバリデーション
    • validateClassNames() 関数で入力文字列を空白区切りに分割し、正規表現 /^[a-zA-Z_-][a-zA-Z0-9_-]*$/ に一致する文字列のみを残します。
    • 有効なクラス名だけを再結合して保存することで、CSS セレクタとして無効なものを排除。
    • この処理は onBlur(入力欄からフォーカスが外れた時)や、必要に応じて Enter 確定時にも適用します(onChange で適用するとスペースが入力できなくなります)。
    • onChange ではリアルタイムにバリデーションせず、そのまま入力を保持することで、ユーザーがスペースや一時的な不完全な文字列を入力できるようにしています。
// RichText, InspectorControls をインポート
import { useBlockProps, RichText, InspectorControls, } from "@wordpress/block-editor";
// PanelBody, TextControl をインポート
import { PanelBody, TextControl } from "@wordpress/components";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // 属性を分割代入で変数に代入
  const { message, showMessageLabel, hideMessageLabel, customClass } = attributes;
  // クラス名のバリデーション
  const validateClassNames = (value) => {
    if (!value) return "";
    // 空白で分割し、クラス名として有効なものだけ残す
    return value
      .trim()
      .split(/\s+/)
      .filter((cls) => /^[a-zA-Z_-][a-zA-Z0-9_-]*$/.test(cls))
      .join(" ");
  };

  // バリデーションを適用した値を属性に設定する関数
  const applyValidation = (value) => {
    const validValue = validateClassNames(value);
    setAttributes({ customClass: validValue });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title="Button Labels">
          <TextControl
            label="Show Label"
            value={showMessageLabel}
            onChange={(value) => setAttributes({ showMessageLabel: value })}
            placeholder="表示用ボタンのラベル"
          />
          <TextControl
            label="Hide Label"
            value={hideMessageLabel}
            onChange={(value) => setAttributes({ hideMessageLabel: value })}
            placeholder="非表示用ボタンのラベル"
          />
        </PanelBody>

        <PanelBody title="Custom Class">
          <TextControl
            label="Additional Classes"
            value={customClass}
            onChange={(value) => {
              // 入力中はそのまま保存
              setAttributes({ customClass: value });
            }}
            onBlur={(event) => {
              // フォーカス外れ時にバリデーション適用
              applyValidation(event.target.value);
            }}
            // Enterキー確定でもバリデーションを実行する場合(オプション)
            onKeyDown={(event) => {
              if (event.key === "Enter") {
                // Enterキー確定時にもバリデーション適用
                applyValidation(event.target.value);
              }
            }}
            help="スペース区切りで複数のクラスを指定可能"
          />
        </PanelBody>
      </InspectorControls>

      <div {...useBlockProps({ className: customClass || undefined })}>
        <RichText
          tagName="p"
          onChange={(value) => setAttributes({ message: value })}
          value={message}
          placeholder="表示するメッセージを入力してください"
        />
      </div>
    </>
  );
}

この構成により、以下のようなメリットが得られます。

  • ユーザーは自由にメッセージやボタンラベルを変更可能
  • 不正なクラス名が自動で排除され安全性確保
  • HTML 出力がクリーンに保たれる
render.php

以下が render.php の実装のポイントです。

  1. カスタムクラスの適用
    • $attributes['customClass'] に入力があれば get_block_wrapper_attributes() に渡してラッパー要素に追加。
    • 値が空の場合はクラス属性自体を追加せず、クリーンな HTML を生成。
  2. コンテキストデータの定義と受け渡し
    • isOpen は初期状態を false に設定(これまでと同じ)。
    • showMessageLabel と hideMessageLabel は block.json の attributes で定義された値を $attributes 配列から取得。
    • これらを $context_data 配列にまとめ、wp_interactivity_data_wp_context() を使って data-wp-context として HTML に埋め込みます。
    • 理由: view.js(フロントエンドの Interactivity API 側)では直接 block.json の属性を参照できないため、PHP 側からコンテキストとして渡す必要があります。
  3. メッセージ出力の安全性確保
    • RichText の入力は HTML タグ(太字、斜体、リンクなど)を含む可能性があるため、単純な esc_html() では装飾やリンクが失われてしまいます。
    • wp_kses_post() を使い、WordPress 投稿本文と同じタグルールで HTML を許可。
    • これにより、XSS などの危険なタグは除去しつつ、ユーザーが付けた装飾は保持される。
  4. Interactivity API のアクションと状態管理
    • ボタンには data-wp-on--click="actions.toggle" を付与し、クリック時に状態をトグル。
    • data-wp-text="state.toggleText" で現在の状態に応じたラベルを切り替え。
    • メッセージ部分には data-wp-bind--hidden="!context.isOpen" を指定し、isOpen が false の場合は非表示に。
<?php
// カスタムクラスを attributes 配列から取得(空なら出力しない)
$classes = trim($attributes['customClass'] ?? '');
// ラッパー要素にクラスを追加
$wrapper_attributes = get_block_wrapper_attributes(
  $classes ? array('class' => $classes) : array()
);

// フロントエンドに渡すコンテキストデータ
$context_data = array(
  "isOpen" => false,
  'showMessageLabel' => $attributes['showMessageLabel'], // block.json で定義した属性
  'hideMessageLabel' => $attributes['hideMessageLabel'], // block.json で定義した属性
);
?>

<div
  <?php echo $wrapper_attributes; ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context($context_data); ?>>
  <button
    data-wp-on--click="actions.toggle"
    data-wp-text="state.toggleText"></button>

  <p data-wp-bind--hidden="!context.isOpen">
    <!-- // 安全にタグを許可して出力 -->
    <?php echo wp_kses_post($attributes['message']); ?>
  </p>
</div>

wp_kses() で特定の HTML タグ以外を除去

この例では、RichText で生成された p 要素を出力する際に、wp_kses_post() を使用していますが、太字・斜体・リンクくらいしか使わない前提であれば、wp_kses() を使って以下のようにホワイトリスト方式で、許可したタグ・属性だけを残して、それ以外はエスケープするとより堅牢です。

<?php
...
$myMessage = $attributes['message'];

$allowed_tags = array(
  'strong' => array(),
  'em'     => array(),
  'a'      => array(
    'href'   => true,
    'title'  => true,
    'target' => true,
    'rel'    => true,
  ),
  'br'     => array(),
);
?>
...
<p data-wp-bind--hidden="!context.isOpen">
    <?php echo wp_kses( $myMessage, $allowed_tags ); ?>
</p>

aria 属性を追加

以下はアクセシビリティ対応のための aria 属性 を追加したコードです。

<?php
$classes = trim($attributes['customClass'] ?? '');
$wrapper_attributes = get_block_wrapper_attributes(
  $classes ? array('class' => $classes) : array()
);

$context_data = array(
  "isOpen" => false,
  'showMessageLabel' => $attributes['showMessageLabel'],
  'hideMessageLabel' => $attributes['hideMessageLabel'],
);

// ユニークな ID を自動生成(この ID を、ボタンが制御する p 要素に id 属性として付与)
$unique_id = wp_unique_id('p-');
?>

<div
  <?php echo $wrapper_attributes; ?>
  data-wp-interactive="create-block"
  <?php echo wp_interactivity_data_wp_context($context_data); ?>>

  <!-- aria-expanded と aria-controls 属性を追加 -->
  <button
    data-wp-on--click="actions.toggle"
    data-wp-bind--aria-expanded="context.isOpen"
    aria-controls="<?php echo esc_attr($unique_id); ?>"
    data-wp-text="state.toggleText">
  </button>

  <!-- 自動生成された id 属性を追加 -->
  <p
    id="<?php echo esc_attr($unique_id); ?>"
    data-wp-bind--hidden="!context.isOpen"
  >
    <?php echo wp_kses_post($attributes['message']); ?>
  </p>
</div>
  • wp_unique_id() で生成した ID をボタンの aria-controls に指定し、どの要素を制御するのかをスクリーンリーダーに伝える
  • aria-expanded は 要素の開閉状態 を表す属性で、data-wp-bind--aria-expanded="context.isOpen" により、トグル状態に応じて自動で true / false が切り替わる
view.js

view.js ではストアを登録し、その中でボタンラベルを context の状態に応じて返す派生ステート toggleText とボタンがクリックされた際に呼び出されるアクション関数 toggle を定義しています。

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

store("create-block", {
  state: {
    // ボタンラベルを context の状態に応じて返す派生ステート(getter)
    get toggleText() {
      const context = getContext();
      // context.isOpen の状態に応じてボタンラベルを返す
      return context.isOpen
        ? context.hideMessageLabel
        : context.showMessageLabel;
    },
  },
  actions: {
    toggle() {
      const context = getContext();
      context.isOpen = !context.isOpen;
    },
  },
});

以下が実装のポイントです。

ストアの登録

store("create-block", { ... });

"create-block" は render.php 側の data-wp-interactive="create-block" と対応しており、この名前空間で状態とアクションを管理します。

派生ステート(derived state)の利用

state: {
  // toggleText は context.isOpen の状態によって動的に決まる値(派生ステート)
  get toggleText() {
    const context = getContext();
    // context.isOpen の状態に応じてボタンラベルを返す
    return context.isOpen
      ? context.hideMessageLabel
      : context.showMessageLabel;
  },
},
  • toggleText は context.isOpen の状態によって動的に決まる値です(派生ステート)。
  • render.php 側で data-wp-text="state.toggleText" を指定することで、自動的にボタンラベルが更新されます。
  • 直接 attributes を参照するのではなく、render.php から渡された context を参照しているのがポイントです(view.js では直接 block.json の属性を参照できないため)。

アクションによる状態変更

actions: {
  toggle() {
    const context = getContext();
    context.isOpen = !context.isOpen;
  },
},
  • toggle() はボタンのクリックで実行され、isOpen を反転させます。
  • この変更により、toggleText が再計算され、表示ラベルとメッセージ表示状態が自動で切り替わります。

データフローの全体像

block.json  →  render.php(attributes → context)
    ↓
view.js(context を参照して派生値や状態を管理)
    ↓
DOM 更新(data-wp-* 属性で自動反映)

作成したブロックを挿入すると、デフォルトのテキストが表示されますが、テキストを編集することができます(リンクや装飾の追加なども可能)。また、インスペクター(サイドバー)でボタンのラベルを設定したり、独自のクラスを追加することができます。

必要に応じて、block.json ファイルで support プロパティにオプションを追加するだけで、ブロックのテキストの色や背景色を変更する color オプションやパディングとマージンなどを変更する spacing オプションをインスペクターパネルに簡単に追加できます。

"supports": {
  "interactivity": true,
  "color": {},
  "spacing": {
    "margin": true,
    "padding": true,
    "blockGap": true
  }
},

例えば、以下のようなスタイル用のパネルが追加され、色やスペースなどを設定できます。

関連ページ