Essayez sans attendre l'hébergement proposé par WordPress
-15% sur le premier mois avec le code 2025PRESS15AFF

Essayer maintenant

Créer un bloc Gutenberg interactif avec l’API Interactivity de WordPress : guide complet 2026

L’API Interactivity de WordPress, c’est un peu la révolution silencieuse que beaucoup de développeurs n’ont pas encore vraiment adoptée. Pourtant, elle change radicalement la façon dont on construit des blocs Gutenberg dynamiques, sans jQuery ni bundle JS lourd à gérer. Dans ce guide, on va construire ensemble un bloc accordéon FAQ fonctionnel, du store au rendu serveur, en passant par le debug et l’optimisation des performances.

L’API Interactivity de WordPress : ce qu’elle change vraiment

Introduite avec WordPress 6.5 et vraiment stabilisée en 2026, l’API Interactivity représente un tournant dans la façon dont on ajoute de l’interactivité aux blocs Gutenberg. Ce n’est pas juste un nouveau helper JavaScript : c’est un choix architectural fort de la part de l’équipe Core de WordPress.

Pourquoi l’API Interactivity plutôt que du JavaScript classique ?

Pendant longtemps, on avait deux options pour rendre un bloc dynamique : soit on balançait du jQuery avec wp_enqueue_script, soit on intégrait React ou Vue en standalone dans son bloc. Les deux approches fonctionnent, mais elles ont un défaut majeur : elles ne s’intègrent pas bien dans l’écosystème WordPress moderne et peuvent vite devenir un cauchemar à maintenir.

L’API Interactivity change la donne. Elle repose sur un runtime léger (basé sur Preact sous le capot), optimisé pour le rendu côté serveur et le streaming HTML. Concrètement, ça veut dire que votre bloc reste performant, compatible avec le cache full-page, et qu’il ne casse pas le SSR. Et franchement, quand on compare ça à l’approche « j’enqueue un gros bundle React de 40ko pour afficher un accordéon », le gain est évident.

Autre avantage non négligeable : tous les blocs qui utilisent l’API partagent le même runtime. Un seul chargement, plusieurs blocs qui en profitent. C’est exactement ce genre de décision qui fait la différence en production.

Les concepts clés : stores, directives et contexte

L’API Interactivity repose sur trois piliers qu’il faut bien comprendre avant de se lancer.

Les stores : pensez-y comme un état global partagé entre tous vos blocs sur la page. Vous définissez des données (state), des actions et des callbacks dans un store, et n’importe quel bloc qui utilise le même namespace peut y accéder et le modifier. C’est le coeur du système réactif.

Les directives : ce sont des attributs HTML déclaratifs que vous ajoutez directement dans le markup de votre bloc. Les principales à retenir :

  • data-wp-bind : lie un attribut HTML à une valeur du store (par exemple, data-wp-bind--hidden="state.isOpen")
  • data-wp-on : attache un écouteur d’événement à une action du store (type data-wp-on--click="actions.toggle")
  • data-wp-context : définit un contexte local au bloc, indépendant du store global
  • data-wp-text : met à jour le contenu textuel d’un élément dynamiquement

Le contexte : c’est la notion la plus subtile. Là où le store est global, le contexte est local à chaque instance de bloc. Si vous avez trois accordéons sur la même page, chacun a son propre contexte (ouvert/fermé), mais ils peuvent tous partager le même store pour des données communes. La distinction est importante et évite pas mal de bugs.

Compatibilité et prérequis en 2026

Avant de vous lancer, voici ce dont vous avez besoin :

  • WordPress 6.5 minimum : c’est la version qui a introduit l’API en version stable. Mais pour bénéficier des dernières améliorations (notamment le support amélioré des contextes imbriqués et les nouvelles directives), visez WordPress 6.8+.
  • Node.js 20+ : les versions antérieures ne sont plus supportées par @wordpress/scripts dans ses dernières itérations.
  • @wordpress/scripts en version récente : vérifiez votre package.json, on recommande la version 30+ pour 2026.
  • La directive "interactivity": true dans block.json : c’est le sésame qui active le runtime. Sans ça, vos directives ne seront tout simplement pas traitées.

Un point à ne pas négliger : l’API Interactivity est conçue pour les blocs côté frontend. Elle ne s’applique pas dans l’éditeur Gutenberg (le backend). Pour l’éditeur, on reste sur les composants React classiques de @wordpress/components. C’est une limite à avoir en tête dès le départ.

Créer un bloc Gutenberg interactif de A à Z

On va construire ensemble un bloc « accordéon FAQ » : chaque question peut s’ouvrir ou se fermer au clic, avec une petite animation. C’est l’exemple parfait pour apprendre l’API Interactivity sans se noyer dans la complexité. Assez simple pour être compris rapidement, assez réaliste pour être utile en production.

Initialiser le bloc avec @wordpress/create-block

La commande à lancer en 2026 est la suivante :

npx @wordpress/create-block@latest mon-bloc-faq --template @wordpress/create-block-interactivity-template

Cette commande génère une structure de bloc préconfigurée pour l’API Interactivity. C’est important d’utiliser --template @wordpress/create-block-interactivity-template et pas juste create-block tout seul : sans ce template, vous n’aurez pas les fichiers view.js et render.php prêts à l’emploi.

Une fois la commande terminée, on se retrouve avec cette arborescence :

mon-bloc-faq/
├── src/
│   ├── block.json
│   ├── edit.js
│   ├── editor.scss
│   ├── index.js
│   ├── render.php
│   ├── style.scss
│   └── view.js
├── package.json
└── ...

Ensuite, on installe les dépendances et on lance le build en mode watch :

cd mon-bloc-faq
npm install
npm start

Configurer block.json pour l’interactivité

C’est là que beaucoup de développeurs se trompent, donc lisez bien cette partie. Le block.json d’un bloc interactif a deux particularités importantes par rapport à un bloc classique.

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "mon-plugin/mon-bloc-faq",
  "title": "FAQ Accordéon",
  "category": "text",
  "description": "Un bloc FAQ avec ouverture/fermeture interactive.",
  "version": "1.0.0",
  "textdomain": "mon-bloc-faq",
  "render": "file:./render.php",
  "viewScriptModule": "file:./view.js",
  "style": "file:./style-index.css",
  "editorStyle": "file:./index.css",
  "editorScript": "file:./index.js",
  "supports": {
    "interactivity": true
  }
}

Deux points critiques ici :

  • "render": "file:./render.php" : on délègue le rendu frontend à un fichier PHP. C’est le mode « bloc dynamique », indispensable pour l’API Interactivity.
  • "viewScriptModule": "file:./view.js" : et pas viewScript ! La différence est fondamentale. viewScript charge un script classique (CommonJS), alors que viewScriptModule charge un module ES natif. L’API Interactivity repose sur les ES modules, donc confondre les deux casse tout… sans forcément afficher d’erreur claire.
  • "supports": { "interactivity": true } : ce champ active explicitement le support de l’API. Sans lui, les directives data-wp-* ne seront pas traitées.

Écrire le store et les directives côté frontend

Le fichier view.js contient la logique interactive du bloc. Pour notre accordéon FAQ, on a besoin d’un état isOpen et d’une action toggle :

// src/view.js
import { store, getContext } from '@wordpress/interactivity';

store( 'mon-plugin/mon-bloc-faq', {
  actions: {
    /**
     * Bascule l'état ouvert/fermé d'une entrée FAQ.
     * On utilise getContext() pour récupérer le contexte
     * propre à chaque entrée, pas un état global.
     */
    toggle() {
      const context = getContext();
      context.isOpen = ! context.isOpen;
    },
  },
} );

Quelques explications : on utilise getContext() et pas un état global dans le store, parce qu’on veut que chaque question FAQ gère son propre état d’ouverture indépendamment des autres. C’est exactement à ça que sert le contexte local dans l’API Interactivity (on en a parlé dans la première partie).

Et si vous avez besoin d’un état initial côté serveur (par exemple, une question ouverte par défaut), vous pouvez l’initialiser dans render.php via wp_interactivity_state(). On y revient juste après.

Gérer le rendu serveur avec render.php

Pourquoi render.php plutôt que save.js ? Deux raisons principales. D’abord, les blocs dynamiques interactifs ont souvent besoin de données issues de la base (articles, ACF, options…). Ensuite, le rendu serveur permet d’injecter l’état initial via PHP, ce qui est beaucoup plus propre que de passer des variables globales en JS.

Voici le fichier render.php pour notre accordéon FAQ :

<?php
/**
 * render.php - Rendu serveur du bloc FAQ Accordéon
 *
 * $attributes contient les attributs du bloc (définis dans block.json)
 * $content contient le contenu des blocs enfants (innerBlocks)
 * $block est l'instance WP_Block
 */

// On définit les questions FAQ (ici en dur pour l'exemple,
// mais en vrai ça viendrait des attributs du bloc)
$faq_items = [
  [ 'question' => 'Qu\'est-ce que l\'API Interactivity ?', 'answer' => 'C\'est le nouveau système officiel de WordPress pour gérer les interactions frontend dans les blocs Gutenberg.' ],
  [ 'question' => 'Faut-il connaître React pour l\'utiliser ?', 'answer' => 'Non ! L\'API Interactivity fonctionne avec des directives HTML et un store JS léger, sans JSX.' ],
];

// Initialisation de l'état global si besoin
// (utile pour partager des données entre plusieurs blocs)
wp_interactivity_state( 'mon-plugin/mon-bloc-faq', [
  'loaded' => true,
] );
?>

<div
  <?php echo get_block_wrapper_attributes(); ?>
  data-wp-interactive="mon-plugin/mon-bloc-faq"
>
  <?php foreach ( $faq_items as $index => $item ) : ?>
    <div
      class="faq-item"
      data-wp-context='<?php echo wp_json_encode( [ "isOpen" => false ] ); ?>'
    >
      <button
        class="faq-question"
        data-wp-on--click="actions.toggle"
        data-wp-class--active="context.isOpen"
        aria-expanded="false"
        data-wp-bind--aria-expanded="context.isOpen"
      >
        <?php echo esc_html( $item['question'] ); ?>
      </button>
      <div
        class="faq-answer"
        data-wp-bind--hidden="!context.isOpen"
      >
        <p><?php echo esc_html( $item['answer'] ); ?></p>
      </div>
    </div>
  <?php endforeach; ?>
</div>

Quelques détails sur les directives utilisées ici :

  • data-wp-interactive : déclare le namespace du store à utiliser pour ce bloc.
  • data-wp-context : initialise le contexte local de chaque item (ici isOpen: false).
  • data-wp-on--click : écoute le clic et appelle actions.toggle.
  • data-wp-class--active : ajoute la classe CSS active quand context.isOpen est true.
  • data-wp-bind--hidden : masque la réponse quand la question est fermée.

Erreur fréquente : oublier d’appeler wp_interactivity_state() côté PHP quand votre store JS s’attend à un état initial. Si la fonction n’est pas appelée, les valeurs d’état seront undefined au chargement de la page et vous aurez des comportements imprévisibles. Pensez aussi à vérifier que get_block_wrapper_attributes() est bien présent dans la balise wrapper : sans lui, les classes CSS et les attributs d’accessibilité générés par WordPress ne seront pas appliqués.

Bonnes pratiques, debug et optimisation des performances

On a maintenant un bloc fonctionnel. Mais avant de passer en production, il y a deux étapes qu’on a tendance à négliger : le debug propre et l’analyse des performances. Et pourtant, ce sont souvent ces étapes qui font la différence entre un bloc qui « marche à peu près » et un bloc vraiment solide.

Déboguer un bloc interactif : outils et erreurs courantes

La bonne nouvelle, c’est que l’API Interactivity génère des messages d’erreur assez explicites dans la console du navigateur. La mauvaise nouvelle, c’est qu’il faut encore savoir les interpréter.

Les outils disponibles :

En 2026, le premier réflexe c’est d’ouvrir les DevTools du navigateur et de regarder l’onglet Console. L’API Interactivity loggue ses avertissements directement là. Pour aller plus loin, vous pouvez utiliser console.log directement dans les actions de votre store :

const { state, getContext } = store('mon-plugin/faq', {
  actions: {
    toggleItem() {
      const context = getContext();
      console.log('État actuel :', context.isOpen);
      context.isOpen = !context.isOpen;
    }
  }
});

Côté PHP, la fonction wp_interactivity_get_raw_data() est très utile pour inspecter l’état initial injecté dans la page. Vous pouvez l’appeler dans votre render.php et faire un var_dump en mode debug pour vérifier que les données transmises au store correspondent bien à ce que vous attendez.

Les 5 erreurs les plus fréquentes :

  1. Mauvais namespace dans store() : le namespace utilisé dans le JS doit correspondre exactement à celui déclaré dans data-wp-interactive. Une majuscule de trop, un tiret oublié… et rien ne fonctionne.


  2. Oubli de data-wp-interactive sur l’élément racine : c’est l’erreur classique du débutant. Sans cet attribut sur le conteneur parent, les directives ne sont jamais initialisées. La console affiche alors « Cannot find store for directive ».


  3. Conflit de namespace entre plusieurs blocs : si deux blocs utilisent le même namespace (par exemple mon-plugin/bloc), leurs stores fusionnent silencieusement, ce qui peut provoquer des comportements imprévisibles. Toujours utiliser un namespace unique par bloc.


  4. Directives mal orthographiées : data-wp-on--clik au lieu de data-wp-on--click, ça arrive. L’API ne remonte pas toujours d’erreur explicite dans ce cas, le comportement est juste ignoré.


  5. Utilisation de viewScript au lieu de viewScriptModule : comme on l’a vu dans la section précédente, l’API Interactivity nécessite obligatoirement viewScriptModule dans block.json. Avec viewScript, le module ES n’est pas chargé correctement et le store reste inaccessible.


Performance et impact sur le Core Web Vitals

C’est là que l’API Interactivity montre vraiment ses avantages par rapport aux approches classiques.

Concrètement, l’API repose sur des modules ES natifs (avec import/export). Le navigateur les charge de façon asynchrone et ne bloque pas le rendu principal. Résultat : le TBT (Total Blocking Time) reste très bas, ce qui améliore mécaniquement l’INP (Interaction to Next Paint) et indirectement le LCP. Deux métriques Core Web Vitals particulièrement surveillées en 2026, notamment depuis que Google a renforcé leur poids dans le classement.

Ce qu’il faut garder en tête :

  • Les modules ES sont chargés en lazy loading natif : le code du store n’est exécuté que si le bloc est présent sur la page. Pas de JS inutile chargé globalement.
  • Évitez d’abuser des stores globaux. Si un état est propre à une instance de bloc (comme l’ouverture d’un accordéon), utilisez getContext() plutôt qu’un état global dans state. C’est plus propre et ça évite les effets de bord entre plusieurs instances d’un même bloc sur la même page.
  • Gardez vos stores légers. L’API Interactivity n’est pas conçue pour remplacer React ou Vue dans des cas complexes.

Bon, soyons réalistes : l’API Interactivity est excellente pour des interactions légères à modérées (accordéons, onglets, filtres, formulaires dynamiques…). Par contre, si vous avez besoin d’embarquer une véritable SPA avec un routing complexe, des appels API imbriqués et une gestion d’état avancée, un bloc React classique avec @wordpress/element reste une option tout à fait valide. Les deux approches coexistent bien dans WordPress, et il n’y a pas de raison de s’interdire l’une ou l’autre selon le contexte.