Depuis l’introduction des nested elements (Tabs et Accordion imbriqués), Elementor propose une base spécifique pour construire ce type de widget : Widget_Nested_Base. Contrairement à un widget classique qui hérite de Widget_Base et affiche un contenu figé par ses propres contrôles, un widget imbriqué délègue une partie de son contenu à des containers enfants que l’utilisateur peut remplir librement avec n’importe quel widget Elementor.
Pour un client qui voulait un widget « Étapes » présentant un processus en plusieurs blocs numérotés, chacun pouvant contenir un texte libre, une image ou même un bouton, la solution la plus propre était de construire ce widget sur ce modèle plutôt que de multiplier les champs répéteur classiques.
Structure de base de la classe PHP
Un widget imbriqué étend \Elementor\Widget_Nested_Base et doit définir le nombre de conteneurs enfants ainsi que leurs identifiants via la méthode get_default_children_elements ou par un contrôle répéteur qui pilote leur nombre :
class Widget_Etapes extends \Elementor\Widget_Nested_Base {
public function get_name() {
return 'widget-etapes';
}
public function get_title() {
return __( 'Étapes', 'mon-theme' );
}
public function get_icon() {
return 'eicon-numbered-list';
}
protected function register_controls() {
$repeater = new \Elementor\Repeater();
$repeater->add_control(
'titre_etape',
[
'label' => __( 'Titre de l\'étape', 'mon-theme' ),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => __( 'Étape', 'mon-theme' ),
]
);
$this->add_control(
'etapes',
[
'label' => __( 'Étapes', 'mon-theme' ),
'type' => \Elementor\Controls_Manager::REPEATER,
'fields' => $repeater->get_controls(),
'prevent_empty' => true,
'default' => [
[ 'titre_etape' => __( 'Analyse', 'mon-theme' ) ],
[ 'titre_etape' => __( 'Conception', 'mon-theme' ) ],
],
]
);
}
}
Récupérer et rendre les containers enfants
La particularité de Widget_Nested_Base tient à sa méthode get_default_children_elements, qui définit combien de containers enfants existent, en fonction par exemple du nombre d’entrées du répéteur. Chaque container enfant reçoit un identifiant stable qu’il faut utiliser pour son rendu PHP :
protected function get_default_children_elements() {
$repeater_items = $this->get_settings( 'etapes' );
$children = [];
foreach ( $repeater_items as $index => $item ) {
$children[] = [
'_element_id' => 'etape-' . $index,
];
}
return $children;
}
protected function content_template() {
// Le rendu JS répète la même logique via Backbone/Marionette.
}

Le rendu final du widget doit ensuite parcourir les enfants avec print_child( $index ) pour afficher le container correspondant à l’endroit voulu dans le HTML :