Engineering Custom Elementor Widgets: The Controls Stack, Group Classes, and Data Repeaters
Unlock the full potential of Elementor's extensibility API. This technical reference walks through the complete lifecycle of custom widget development—from class instantiation and registration to the granular implementation of the Controls Stack. We cover syntax for basic and advanced inputs, leverage Group Controls for standardized styling (Typography, Borders), and implement the Repeater class for iterable data structures, ensuring your custom components function natively within the editor.
Widget Development Basics
Widget File Structure
An Elementor widget follows a specific file organization pattern within your plugin or theme. The widget class file is
typically placed in a dedicated widgets folder, and must be properly included and registered with Elementor's widget
manager during the elementor/widgets/widgets_registered action hook.
my-elementor-addon/ ├── my-elementor-addon.php # Main plugin file ├── widgets/ │ ├── my-widget.php # Widget class file │ └── another-widget.php ├── assets/ │ ├── css/ │ └── js/ └── includes/ └── plugin.php # Plugin loader
Widget Class Creation
A widget class is a PHP class that encapsulates all widget functionality including controls definition, rendering logic,
and editor preview. The class must extend \Elementor\Widget_Base and implement required abstract methods to define the
widget's behavior and appearance in both editor and frontend.
<?php namespace MyAddon\Widgets; class My_Custom_Widget extends \Elementor\Widget_Base { // Widget methods go here public function get_name() { return 'my-widget'; } public function get_title() { return 'My Widget'; } protected function render() { echo '<div>Hello World</div>'; } }
Extending Widget_Base
Widget_Base is the abstract base class provided by Elementor that all custom widgets must extend. It provides the core
infrastructure for controls registration, rendering, content template generation, and integration with Elementor's
editor—you override its methods to customize your widget's behavior.
<?php use Elementor\Widget_Base; class My_Widget extends Widget_Base { // Must implement: get_name(), get_title() // Should implement: get_icon(), get_categories() // Core methods: register_controls(), render(), content_template() }
Widget Registration
Widget registration tells Elementor about your custom widget so it appears in the editor panel. This is done by hooking
into elementor/widgets/register (Elementor 3.5+) or elementor/widgets/widgets_registered (legacy) and calling the
register() method on the widgets manager with your widget instance.
<?php add_action('elementor/widgets/register', function($widgets_manager) { require_once(__DIR__ . '/widgets/my-widget.php'); $widgets_manager->register(new \My_Custom_Widget()); });
get_name() Method
Returns the unique programmatic identifier (slug) for your widget, used internally by Elementor for widget identification, data storage, and CSS class generation. This should be lowercase with hyphens, without spaces, and must be unique across all registered widgets.
public function get_name() { return 'my-custom-heading'; // Unique slug identifier } // Used in: CSS classes, data attributes, internal references // Output: elementor-widget-my-custom-heading
get_title() Method
Returns the human-readable display name shown in Elementor's widget panel and editor interface. This is what users see when browsing available widgets, so it should be descriptive, properly capitalized, and optionally translatable using WordPress i18n functions.
public function get_title() { return esc_html__('My Custom Heading', 'my-text-domain'); }
┌─────────────────────────┐ │ ELEMENTS PANEL │ ├─────────────────────────┤ │ 🔤 My Custom Heading │ ← Title shown here │ 📝 Text Editor │ │ 🖼️ Image │ └─────────────────────────┘
get_icon() Method
Returns the CSS class for the icon displayed next to your widget in the Elementor panel. You can use Elementor's
eicon-* icons, WordPress Dashicons (dashicons-*), Font Awesome (fa fa-*), or custom icon classes from your own
icon font.
public function get_icon() { return 'eicon-heading'; // Elementor icon // return 'dashicons-admin-post'; // Dashicon // return 'fa fa-star'; // Font Awesome } // Common eicons: eicon-heading, eicon-text, eicon-image, // eicon-button, eicon-gallery-grid
Elementor default icons (eicon‑)
| Icon name | Description |
|---|---|
| eicon-editor-code | eicon-code – generic code icon |
| eicon-editor-bold | eicon-bold – bold text |
| eicon-editor-italic | eicon-italic – italic text |
| eicon-editor-underline | eicon-underline – underline |
| eicon-editor-alignleft | eicon-align-left – left align |
| eicon-editor-aligncenter | eicon-align-center – center align |
| eicon-editor-alignright | eicon-align-right – right align |
| eicon-editor-alignjustify | eicon-align-justify – justify |
| eicon-editor-list-ul | eicon-list-ul – unordered list |
| eicon-editor-list-ol | eicon-list-ol – ordered list |
| eicon-editor-link | eicon-link – hyperlink |
| eicon-editor-unlink | eicon-unlink – remove link |
| eicon-editor-table | eicon-table – table |
| eicon-editor-insertimage | eicon-image – image insert |
| eicon-editor-video | eicon-video – video insert |
| eicon-editor-insertfile | eicon-file – file insert |
| eicon-editor-insertmedia | eicon-media – media library |
| eicon-editor-quote | eicon-quote – blockquote |
| eicon-editor-code | eicon-code – code block |
| eicon-editor-paragraph | eicon-paragraph – paragraph |
| eicon-editor-heading | eicon-heading – heading |
| eicon-editor-hr | eicon-hr – horizontal rule |
| eicon-editor-strikethrough | eicon-strikethrough – strikethrough |
| eicon-editor-subscript | eicon-subscript – subscript |
| eicon-editor-superscript | eicon-superscript – superscript |
| eicon-editor-undo | eicon-undo – undo |
| eicon-editor-redo | eicon-redo – redo |
| eicon-editor-cut | eicon-cut – cut |
| eicon-editor-copy | eicon-copy – copy |
| eicon-editor-paste | eicon-paste – paste |
| eicon-editor-selectall | eicon-select-all – select all |
| eicon-editor-clearformat | eicon-clear-format – clear formatting |
| eicon-editor-spellcheck | eicon-spellcheck – spell check |
| eicon-editor-emoji | eicon-emoji – emoji |
| eicon-editor-help | eicon-help – help/info |
| eicon-editor-settings | eicon-settings – settings |
| eicon-editor-search | eicon-search – search |
| eicon-editor-close | eicon-close – close |
| eicon-editor-check | eicon-check – check/confirm |
| eicon-editor-plus | eicon-plus – add |
| eicon-editor-minus | eicon-minus – remove |
| eicon-editor-arrow-up | eicon-arrow-up – up arrow |
| eicon-editor-arrow-down | eicon-arrow-down – down arrow |
| eicon-editor-arrow-left | eicon-arrow-left – left arrow |
| eicon-editor-arrow-right | eicon-arrow-right – right arrow |
| eicon-editor-star | eicon-star – star/favorite |
| eicon-editor-heart | eicon-heart – heart/like |
| eicon-editor-bell | eicon-bell – notifications |
| eicon-editor-calendar | eicon-calendar – calendar |
| eicon-editor-clock | eicon-clock – clock/time |
| eicon-editor-user | eicon-user – user/profile |
| eicon-editor-users | eicon-users – multiple users |
| eicon-editor-lock | eicon-lock – lock/security |
| eicon-editor-unlock | eicon-unlock – unlock |
| eicon-editor-key | eicon-key – key |
| eicon-editor-wrench | eicon-wrench – tools |
| eicon-editor-cog | eicon-cog – settings gear |
| eicon-editor-trash | eicon-trash – delete |
| eicon-editor-download | eicon-download – download |
| eicon-editor-upload | eicon-upload – upload |
| eicon-editor-share | eicon-share – share |
| eicon-editor-link-external | eicon-external-link – external link |
| eicon-editor-globe | eicon-globe – globe/internet |
| eicon-editor-map-pin | eicon-map-pin – location pin |
| eicon-editor-phone | eicon-phone – phone |
| eicon-editor-email | eicon-email – email |
| eicon-editor-chat | eicon-chat – chat/message |
| eicon-editor-comment | eicon-comment – comment |
| eicon-editor-quote-left | eicon-quote-left – opening quote |
| eicon-editor-quote-right | eicon-quote-right – closing quote |
| eicon-editor-quote-alt | eicon-quote-alt – alternate quote |
| eicon-editor-quote-alt2 | eicon-quote-alt2 – second alternate |
| eicon-editor-quote-alt3 | eicon-quote-alt3 – third alternate |
| eicon-editor-quote-alt4 | eicon-quote-alt4 – fourth alternate |
| eicon-editor-quote-alt5 | eicon-quote-alt5 – fifth alternate |
| eicon-editor-quote-alt6 | eicon-quote-alt6 – sixth alternate |
| eicon-editor-quote-alt7 | eicon-quote-alt7 – seventh alternate |
| eicon-editor-quote-alt8 | eicon-quote-alt8 – eighth alternate |
| eicon-editor-quote-alt9 | eicon-quote-alt9 – ninth alternate |
| eicon-editor-quote-alt10 | eicon-quote-alt10 – tenth alternate |
get_categories() Method
Returns an array of category slugs where your widget should appear in the Elementor panel. Built-in categories include
basic, general, pro-elements, theme-elements, and woocommerce-elements, you can also register custom
categories
for grouping your widgets.
public function get_categories() { return ['basic', 'general']; // Appears in both categories } // Registering custom category: add_action('elementor/elements/categories_registered', function($elements_manager) { $elements_manager->add_category('my-category', [ 'title' => 'My Widgets', 'icon' => 'fa fa-plug', ]); });
get_keywords() Method
Returns an array of search keywords that help users find your widget when typing in the Elementor panel search box. Include synonyms, related terms, and common misspellings to improve widget discoverability beyond just the widget title.
public function get_keywords() { return ['heading', 'title', 'text', 'headline', 'h1', 'header']; }
Search: "headline" ↓ ┌──────────────────────┐ │ 🔍 headline │ ├──────────────────────┤ │ 🔤 My Custom Heading │ ← Matched via keywords │ 📝 Heading │ └──────────────────────┘
Widget Controls
Controls Introduction
Controls are the UI elements in Elementor's editor panel that allow users to customize widget settings—they range from
simple text inputs to complex color pickers and media uploaders. Each control type stores a value that you retrieve in
the render() method using $this->get_settings_for_display() to generate dynamic output.
┌─────────────────────────────────────────┐ │ WIDGET PANEL │ ├─────────────────────────────────────────┤ │ Content Style Advanced │ │ ───────────────────────────────────── │ │ Section: Text Settings │ │ ┌───────────────────────────────────┐ │ │ │ Title: [________________] TEXT │ │ │ │ Color: [■ #333333 ] COLOR │ │ │ │ Size: [ 16 ] px NUMBER │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘
start_controls_section()
Opens a new collapsible section in the editor panel to group related controls together. You must specify a unique
section ID, label, and the tab where it appears (content, style, or advanced). Every start_controls_section()
must have a corresponding end_controls_section().
$this->start_controls_section( 'content_section', // Unique section ID [ 'label' => esc_html__('Content', 'my-domain'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // Other tabs: TAB_STYLE, TAB_ADVANCED ] ); // Add controls here... $this->end_controls_section();
end_controls_section()
Closes a previously opened controls section—it's mandatory and must be called after start_controls_section() once
you've finished adding all controls to that section. Forgetting this call will cause PHP errors and break the widget
editor interface.
$this->start_controls_section('section_content', [...]); $this->add_control('title', [...]); $this->add_control('description', [...]); $this->end_controls_section(); // ← Required closure $this->start_controls_section('section_style', [...]); // New section controls... $this->end_controls_section(); // ← Each section needs its own close
add_control()
The primary method for adding a single control element to the current section. It takes a unique control ID, an array of
arguments defining the control type, label, default value, and other options—the stored value is accessed in render via
$settings['control_id'].
$this->add_control( 'widget_title', // Control ID (used to get value) [ 'label' => esc_html__('Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__('Default Title', 'my-domain'), 'placeholder' => esc_html__('Enter title', 'my-domain'), ] ); // In render(): $settings = $this->get_settings_for_display(); echo '<h2>' . esc_html($settings['widget_title']) . '</h2>';
Control Types Overview
Elementor provides numerous control types accessed via Controls_Manager constants, categorized as: Data controls (
TEXT, NUMBER, URL), UI controls (SELECT, SWITCHER, CHOOSE), Multi-value controls (DIMENSIONS, SLIDER), Unit
controls (TYPOGRAPHY, BOX_SHADOW), and Group controls for complex styling options.
┌─────────────────────────────────────────────────────────┐ │ CONTROL TYPES │ ├───────────────┬───────────────┬─────────────────────────┤ │ Data │ UI │ Complex │ ├───────────────┼───────────────┼─────────────────────────┤ │ TEXT │ SELECT │ SLIDER │ │ TEXTAREA │ SELECT2 │ DIMENSIONS │ │ NUMBER │ SWITCHER │ MEDIA │ │ WYSIWYG │ CHOOSE │ GALLERY │ │ URL │ COLOR │ REPEATER │ │ CODE │ DATE_TIME │ ICONS │ │ HIDDEN │ BUTTON │ Group Controls │ └───────────────┴───────────────┴─────────────────────────┘
Elementor Control Types (Controls_Manager)
| Control constant | Category | Typical use / brief explanation |
|---|---|---|
| TEXT | Data | Single‑line text input for strings (e.g., CSS class, custom ID). |
| TEXTAREA | Data | Multi‑line text area, often for custom HTML, CSS or long descriptions. |
| NUMBER | Data | Numeric input with optional min/max/step; used for sizes, counts, etc. |
| URL | Data | Input that validates a URL; automatically adds http:// if missing. |
| SELECT | UI | Dropdown list of predefined options; good for choosing a style preset. |
| SWITCHER | UI | On/off toggle (boolean); renders as a modern switch UI. |
| CHOOSE | UI | Visual choice set (icons or images) for options like alignment, position. |
| COLOR | UI | Color picker (hex, rgba, gradient); often paired with opacity control. |
| MEDIA | UI | Opens the WordPress media library to select images, videos, or files. |
| ICON | UI | Icon selector (Font Awesome, Elementor icons, custom SVG). |
| SLIDER | Multi‑value | Slider bar for numeric ranges; can show value tooltip while dragging. |
| DIMENSIONS | Multi‑value | Four‑field input (top, right, bottom, left) for margins, paddings, borders. |
| BORDER | Multi‑value | Combines width, style, and color for border definitions. |
| BOX_SHADOW | Unit | Controls horizontal offset, vertical offset, blur, spread, and color of a shadow. |
| TYPOGRAPHY | Unit | Group control covering font family, size, weight, line‑height, letter‑spacing, text‑transform, and style. |
| HEADING | UI | Non‑interactive label used to separate sections inside the panel. |
| RAW_HTML | UI | Displays raw HTML (help text, links, documentation) inside the control panel. |
| CODE | UI | Code editor with syntax highlighting (HTML, CSS, JS) for custom snippets. |
| REPEATER | Complex | Allows repeating a set of sub‑controls (e.g., list items, slides). |
| GROUP_CONTROL | Group | Base class for complex styling groups (e.g., Group_Control_Typography, Group_Control_Background). |
| BACKGROUND_GROUP | Group | Handles background types (classic, gradient, video) and related options. |
| IMAGE_DIMENSIONS | Multi‑value | Width × height selector for image size, often with a “custom” option. |
| DATE_TIME | Data | Date and/or time picker, stored as a string in ISO format. |
| HIDDEN | Data | Invisible field used to store internal values; not shown to the user. |
| POPUP | UI | Opens a modal popup to configure nested settings (used in advanced widgets). |
| TABS | UI | Organises controls into tabbed sections for better UX. |
| SECTION | UI | Visual separator that groups related controls under a collapsible heading. |
Control Arguments
Each control accepts an array of arguments that configure its behavior: label (display name), type (control type
constant), default (initial value), placeholder, description, separator, condition, selectors, and
type-specific options like options for SELECT or min/max for NUMBER.
$this->add_control( 'example_control', [ 'label' => esc_html__('Label', 'domain'), // Display text 'type' => Controls_Manager::TEXT, // Control type 'default' => 'Default value', // Initial value 'placeholder' => 'Placeholder text', // Hint text 'description' => 'Help text below control', // Help text 'label_block' => true, // Full-width label 'separator' => 'before', // Visual separator 'classes' => 'custom-css-class', // Custom class ] );
Conditions
Conditions allow you to show or hide controls based on other control values, creating dynamic and contextual editor
interfaces. You can use simple equality checks, multiple conditions (AND logic), or the conditions key for complex
OR/AND combinations.
$this->add_control('show_title', [ 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ]); $this->add_control('title_text', [ 'type' => Controls_Manager::TEXT, 'condition' => [ 'show_title' => 'yes', // Simple condition ], ]); $this->add_control('title_color', [ 'type' => Controls_Manager::COLOR, 'condition' => [ 'show_title' => 'yes', 'title_text!' => '', // NOT empty (! operator) ], ]);
Selectors
Selectors enable controls to directly output CSS properties without PHP processing, connecting control values to CSS
rules using placeholders like {{VALUE}}, {{UNIT}}, and {{SIZE}}. This is the recommended approach for style
controls as it leverages Elementor's CSS caching system.
$this->add_control( 'title_color', [ 'label' => 'Color', 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .widget-title' => 'color: {{VALUE}};', '{{WRAPPER}} .widget-title a' => 'color: {{VALUE}};', ], ] ); // {{WRAPPER}} = .elementor-element-{element-id} // {{VALUE}} = The control's stored value
Default Values
Default values define the initial state of a control when a widget is first added to the page. The format varies by control type—simple values for basic controls, arrays for complex controls like DIMENSIONS or SLIDER, and proper localization should be applied for translatable text defaults.
// Simple default 'default' => 'Hello World', // Slider default with unit 'default' => [ 'size' => 50, 'unit' => 'px', ], // Dimensions default 'default' => [ 'top' => '10', 'right' => '20', 'bottom' => '10', 'left' => '20', 'unit' => 'px', 'isLinked' => false, ], // URL default 'default' => [ 'url' => 'https://example.com', 'is_external' => true, ],
Basic Controls
TEXT Control
A single-line text input field for short text content like headings, labels, or custom CSS classes. Supports
placeholder, label_block for full-width display, dynamic tags for Elementor Pro, and input_type attribute for
HTML5 input variations.
$this->add_control( 'widget_title', [ 'label' => esc_html__('Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__('Default Title', 'my-domain'), 'placeholder' => esc_html__('Enter your title', 'my-domain'), 'label_block' => true, 'dynamic' => ['active' => true], // Enable dynamic tags 'ai' => ['active' => false], // Disable AI (Elementor 3.17+) ] );
TEXTAREA Control
A multi-line text input for longer content like descriptions, paragraphs, or custom HTML/CSS snippets. Supports rows
argument to control height, and unlike WYSIWYG, outputs raw text requiring manual HTML escaping in render.
$this->add_control( 'description', [ 'label' => esc_html__('Description', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXTAREA, 'default' => esc_html__('Default description text...', 'my-domain'), 'placeholder' => esc_html__('Enter description', 'my-domain'), 'rows' => 5, // Number of visible rows ] ); // In render(): echo '<p>' . esc_html($settings['description']) . '</p>'; // Or with line breaks preserved: echo '<p>' . nl2br(esc_html($settings['description'])) . '</p>';
NUMBER Control
A numeric input field with optional min, max, and step constraints for precise numerical values. Ideal for
quantities, z-index, animation delays, or any integer/decimal value that doesn't require unit selection (use SLIDER for
values with units).
$this->add_control( 'items_count', [ 'label' => esc_html__('Number of Items', 'my-domain'), 'type' => \Elementor\Controls_Manager::NUMBER, 'min' => 1, 'max' => 100, 'step' => 1, 'default' => 5, ] ); // For decimal values: $this->add_control('opacity', [ 'type' => Controls_Manager::NUMBER, 'min' => 0, 'max' => 1, 'step' => 0.1, 'default' => 1, ]);
URL Control
A specialized input for URLs with additional fields for "open in new window" and "nofollow" options. Returns an array
with url, is_external, nofollow, and custom_attributes keys that should be properly rendered using
$this->add_link_attributes() method.
$this->add_control( 'website_link', [ 'label' => esc_html__('Link', 'my-domain'), 'type' => \Elementor\Controls_Manager::URL, 'placeholder' => esc_html__('https://your-link.com', 'my-domain'), 'default' => [ 'url' => '', 'is_external' => true, 'nofollow' => true, ], 'options' => ['url', 'is_external', 'nofollow'], // Visible options ] ); // In render(): $this->add_link_attributes('button_link', $settings['website_link']); echo '<a ' . $this->get_render_attribute_string('button_link') . '>Click</a>';
WYSIWYG Control
A full WordPress TinyMCE rich text editor for complex formatted content with paragraphs, lists, links, and basic
formatting. The output is already HTML-safe and should be echoed directly; use wp_kses_post() if you need additional
sanitization.
$this->add_control( 'content', [ 'label' => esc_html__('Content', 'my-domain'), 'type' => \Elementor\Controls_Manager::WYSIWYG, 'default' => '<p>' . esc_html__('Default content...', 'my-domain') . '</p>', ] ); // In render() - already contains HTML, safe to output: echo '<div class="content-wrapper">' . $settings['content'] . '</div>'; // Or with extra safety: echo wp_kses_post($settings['content']);
CODE Control
A syntax-highlighted code editor for HTML, CSS, JavaScript, or other code input using CodeMirror. Specify the language
argument for proper syntax highlighting; the value should be properly escaped or sanitized based on the expected code
type.
$this->add_control( 'custom_html', [ 'label' => esc_html__('Custom HTML', 'my-domain'), 'type' => \Elementor\Controls_Manager::CODE, 'language' => 'html', // html, css, javascript, json, etc. 'rows' => 10, 'default' => '<div class="custom">Content</div>', ] ); $this->add_control( 'custom_css', [ 'label' => esc_html__('Custom CSS', 'my-domain'), 'type' => Controls_Manager::CODE, 'language' => 'css', 'default' => '.my-class { color: red; }', ] );
HIDDEN Control
A non-visible control that stores values programmatically without displaying in the editor panel. Useful for storing internal data, version numbers, or computed values that shouldn't be user-editable but need to persist in widget settings.
$this->add_control( 'widget_version', [ 'label' => '', 'type' => \Elementor\Controls_Manager::HIDDEN, 'default' => '1.0.0', ] ); $this->add_control( 'internal_id', [ 'type' => Controls_Manager::HIDDEN, 'default' => uniqid('widget_'), ] ); // Value accessible in render(): $settings['widget_version']
SWITCHER Control
A toggle switch for boolean on/off options that stores yes or empty string (not true/false). Commonly used for
showing/hiding elements, enabling features, or toggling between two states with label_on, label_off, and
return_value customization.
$this->add_control( 'show_title', [ 'label' => esc_html__('Show Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Yes', 'my-domain'), 'label_off' => esc_html__('No', 'my-domain'), 'return_value' => 'yes', // Value when ON 'default' => 'yes', // '' (empty) = OFF ] ); // In render(): if ('yes' === $settings['show_title']) { echo '<h2>' . esc_html($settings['title']) . '</h2>'; }
SELECT Control
A dropdown select menu for choosing one option from a predefined list. Options are defined as an associative array where
keys are stored values and values are display labels; supports multiple for multi-select but SELECT2 is preferred for
that use case.
$this->add_control( 'heading_tag', [ 'label' => esc_html__('HTML Tag', 'my-domain'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'h2', 'options' => [ 'h1' => 'H1', 'h2' => 'H2', 'h3' => 'H3', 'h4' => 'H4', 'p' => 'Paragraph', 'div' => 'Div', 'span' => 'Span', ], ] ); // In render(): $tag = $settings['heading_tag']; echo "<{$tag}>" . esc_html($settings['title']) . "</{$tag}>";
SELECT2 Control
An enhanced dropdown using the Select2 library with search, tagging, and multi-select capabilities. Use
multiple => true for selecting multiple values (returns array); ideal for selecting posts, terms, or any large option
list requiring search functionality.
$this->add_control( 'selected_categories', [ 'label' => esc_html__('Categories', 'my-domain'), 'type' => \Elementor\Controls_Manager::SELECT2, 'multiple' => true, 'options' => $this->get_categories_options(), // Your method 'default' => [], 'label_block' => true, ] ); // Returns array when multiple: ['cat1', 'cat2', 'cat3'] // Returns string when single: 'cat1' private function get_categories_options() { $categories = get_categories(); $options = []; foreach ($categories as $cat) { $options[$cat->term_id] = $cat->name; } return $options; }
CHOOSE Control
A visual icon-based toggle group for selecting from mutually exclusive options like alignment, layout direction, or
position. Each option displays an icon (using Elementor's eicon-* classes) and is ideal for design-related choices
with 2-4 options.
$this->add_control( 'text_align', [ 'label' => esc_html__('Alignment', 'my-domain'), 'type' => \Elementor\Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => esc_html__('Left', 'my-domain'), 'icon' => 'eicon-text-align-left', ], 'center' => [ 'title' => esc_html__('Center', 'my-domain'), 'icon' => 'eicon-text-align-center', ], 'right' => [ 'title' => esc_html__('Right', 'my-domain'), 'icon' => 'eicon-text-align-right', ], ], 'default' => 'center', 'toggle' => true, // Allow deselection 'selectors' => [ '{{WRAPPER}} .title' => 'text-align: {{VALUE}};', ], ] );
┌───────┬────────┬───────┐ │ ≡◀ │ ≡ │ ▶≡ │ ← Visual representation │ Left │ Center │ Right │ └───────┴────────┴───────┘
COLOR Control
A color picker with hex, RGB, HSL input support, optional alpha/opacity slider, and global color integration. Use with
selectors for direct CSS output; returns empty string if no color selected, hex value (#ffffff), or RGBA for
transparent colors.
$this->add_control( 'title_color', [ 'label' => esc_html__('Title Color', 'my-domain'), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#333333', 'alpha' => true, // Enable opacity slider 'global' => [ 'default' => \Elementor\Core\Kits\Documents\Tabs\Global_Colors::COLOR_PRIMARY, ], 'selectors' => [ '{{WRAPPER}} .widget-title' => 'color: {{VALUE}};', ], ] );
┌────────────────────┐ │ Title Color │ │ ┌────┐ │ │ │████│ #3366FF │ ← Color picker with swatch │ └────┘ │ │ [Global ▼] │ ← Global colors dropdown └────────────────────┘
DATE_TIME Control
A date and time picker control using Flatpickr library for selecting specific dates, times, or datetime combinations.
Configure with picker_options based on Flatpickr's API; returns formatted string that you can parse with PHP's
DateTime or strtotime().
$this->add_control( 'event_date', [ 'label' => esc_html__('Event Date', 'my-domain'), 'type' => \Elementor\Controls_Manager::DATE_TIME, 'default' => date('Y-m-d H:i'), 'picker_options' => [ 'enableTime' => true, 'dateFormat' => 'Y-m-d H:i', 'minDate' => 'today', ], ] ); // In render(): $event_date = $settings['event_date']; $timestamp = strtotime($event_date); echo 'Event: ' . date('F j, Y g:i A', $timestamp);
┌───────────────────────────┐ │ Event Date │ │ ┌─────────────────────┐ │ │ │ 2024-12-25 14:30 │ │ │ └─────────────────────┘ │ │ ┌─────────────────────┐ │ │ │ ◀ December 2024 ▶ │ │ │ │ Su Mo Tu We Th Fr Sa│ │ │ │ 1 2 3 4 5 6 7│ │ │ │ 8 9 10 11 12 13 14│ │ │ │ ... [25] ... │ │ │ └─────────────────────┘ │ └───────────────────────────┘
Advanced Controls
MEDIA Control
The MEDIA control allows users to select images or videos from the WordPress Media Library, returning an array with
url and id keys for the selected media file.
$this->add_control( 'image', [ 'label' => esc_html__( 'Choose Image', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::MEDIA, 'default' => [ 'url' => \Elementor\Utils::get_placeholder_image_src(), ], ] ); // Usage: $settings['image']['url'], $settings['image']['id']
GALLERY Control
The GALLERY control enables selection of multiple images from the Media Library, returning an array of image objects, perfect for creating image galleries or sliders.
$this->add_control( 'gallery', [ 'label' => esc_html__( 'Add Images', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::GALLERY, 'default' => [], 'show_label' => false, ] ); // Returns: [ ['id'=>1,'url'=>'...'], ['id'=>2,'url'=>'...'] ]
ICONS Control
The ICONS control provides a comprehensive icon picker supporting Font Awesome, custom icon libraries, and SVG uploads,
returning an array with library and value keys.
$this->add_control( 'selected_icon', [ 'label' => esc_html__( 'Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, 'default' => [ 'value' => 'fas fa-star', 'library' => 'fa-solid', ], ] ); // Render: \Elementor\Icons_Manager::render_icon( $settings['selected_icon'] );
SLIDER Control
The SLIDER control creates a draggable range slider with numeric input, supporting multiple units (px, em, %), min/max values, and step increments for precise value selection.
$this->add_control( 'width', [ 'label' => esc_html__( 'Width', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 1000, 'step' => 5 ], '%' => [ 'min' => 0, 'max' => 100 ], ], 'default' => [ 'unit' => 'px', 'size' => 50 ], 'selectors' => [ '{{WRAPPER}} .element' => 'width: {{SIZE}}{{UNIT}};', ], ] );
DIMENSIONS Control
The DIMENSIONS control provides four linked input fields for padding, margin, or border-radius values with unit selection and the ability to link/unlink values.
$this->add_control( 'margin', [ 'label' => esc_html__( 'Margin', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em' ], 'selectors' => [ '{{WRAPPER}} .element' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] );
┌─────────────────────────────┐
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ T │ │ R │ │ B │ │ L │ 🔗│
│ └───┘ └───┘ └───┘ └───┘ │
└─────────────────────────────┘
HEADING Control
The HEADING control is a UI-only element that displays a styled heading/label in the editor panel to organize and separate control sections visually.
$this->add_control( 'section_heading', [ 'label' => esc_html__( 'Style Options', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::HEADING, 'separator' => 'before', // 'before', 'after', 'none' ] );
RAW_HTML Control
The RAW_HTML control injects custom HTML content directly into the editor panel, useful for displaying instructions, warnings, or custom UI elements that don't store values.
$this->add_control( 'important_note', [ 'type' => \Elementor\Controls_Manager::RAW_HTML, 'raw' => '<div class="notice"> <strong>Note:</strong> Configure API key in settings first. </div>', 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', ] );
BUTTON Control
The BUTTON control renders a clickable button in the editor panel that triggers JavaScript events, commonly used for triggering AJAX actions or opening modals.
$this->add_control( 'sync_button', [ 'label' => esc_html__( 'Sync Data', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::BUTTON, 'text' => esc_html__( 'Click to Sync', 'plugin-name' ), 'event' => 'namespace:editor:sync', // Custom JS event ] );
DIVIDER Control
The DIVIDER control adds a horizontal line separator in the editor panel to visually organize controls into logical groups without affecting functionality.
$this->add_control( 'hr', [ 'type' => \Elementor\Controls_Manager::DIVIDER, ] );
├─────────────────────────────┤
│ Control Above │
├─────────────────────────────┤ ← DIVIDER
│ Control Below │
└─────────────────────────────┘
DEPRECATED_NOTICE Control
The DEPRECATED_NOTICE control displays a standardized deprecation warning in the editor, informing users that a widget or feature is obsolete and suggesting alternatives.
$this->add_control( 'deprecated_notice', [ 'type' => \Elementor\Controls_Manager::DEPRECATED_NOTICE, 'widget' => 'old-widget-name', 'since' => '3.0.0', 'last' => '4.0.0', 'plugin' => 'My Plugin', 'replacement' => 'new-widget-name', ] );
ALERT Control
The ALERT control displays styled notification messages (info, success, warning, danger) in the editor panel to communicate important information to users.
$this->add_control( 'alert_warning', [ 'type' => \Elementor\Controls_Manager::ALERT, 'alert_type' => 'warning', // info, success, warning, danger 'heading' => esc_html__( 'Warning', 'plugin-name' ), 'content' => esc_html__( 'This requires Pro version.', 'plugin-name' ), ] );
Style Controls
FONT Control
The FONT control provides a searchable dropdown of all available fonts including Google Fonts, system fonts, and custom uploaded fonts, returning the font-family name as a string.
$this->add_control( 'font_family', [ 'label' => esc_html__( 'Font Family', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::FONT, 'default' => "'Open Sans', sans-serif", 'selectors' => [ '{{WRAPPER}} .title' => 'font-family: {{VALUE}}', ], ] );
TYPOGRAPHY Control (Group)
The TYPOGRAPHY group control bundles font-family, size, weight, transform, style, decoration, line-height, and letter-spacing into a single cohesive control set for comprehensive text styling.
$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'content_typography', 'label' => esc_html__( 'Typography', 'plugin-name' ), 'selector' => '{{WRAPPER}} .content-text', ] );
┌─ Typography ─────────────────┐
│ Font Family: [Roboto ▼] │
│ Size: [16] [px ▼] │
│ Weight: [400 ▼] │
│ Transform: [None ▼] │
│ Line Height: [1.5] │
│ Letter Spacing: [0] │
└──────────────────────────────┘
TEXT_SHADOW Control (Group)
The TEXT_SHADOW group control provides color, blur, horizontal, and vertical offset settings to apply CSS text-shadow effects to text elements.
$this->add_group_control( \Elementor\Group_Control_Text_Shadow::get_type(), [ 'name' => 'text_shadow', 'label' => esc_html__( 'Text Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .heading', ] ); // Output: text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
BOX_SHADOW Control (Group)
The BOX_SHADOW group control enables configuration of horizontal/vertical offsets, blur, spread, color, and inset options for CSS box-shadow effects on container elements.
$this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'box_shadow', 'label' => esc_html__( 'Box Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .card', ] ); // Output: box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
BORDER Control (Group)
The BORDER group control combines border-style (solid, dashed, dotted, etc.), border-width for each side, and border-color into a unified control for element borders.
$this->add_group_control( \Elementor\Group_Control_Border::get_type(), [ 'name' => 'border', 'label' => esc_html__( 'Border', 'plugin-name' ), 'selector' => '{{WRAPPER}} .box', ] );
BACKGROUND Control (Group)
The BACKGROUND group control supports classic (color/image), gradient, video, and slideshow background types with positioning, sizing, attachment, and overlay options.
$this->add_group_control( \Elementor\Group_Control_Background::get_type(), [ 'name' => 'background', 'label' => esc_html__( 'Background', 'plugin-name' ), 'types' => [ 'classic', 'gradient', 'video' ], 'selector' => '{{WRAPPER}} .section', ] );
Background Type: [Classic] [Gradient] [Video]
┌────────────────────────────────────┐
│ Color: [████████] Image: [Choose] │
│ Position: [Center ▼] │
│ Size: [Cover ▼] │
└────────────────────────────────────┘
CSS_FILTER Control (Group)
The CSS_FILTER group control provides blur, brightness, contrast, saturation, and hue rotation sliders to apply CSS filter effects to images or elements.
$this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'css_filters', 'selector' => '{{WRAPPER}} img', ] ); // Output: filter: blur(2px) brightness(110%) contrast(90%);
IMAGE_DIMENSIONS Control
The IMAGE_DIMENSIONS control provides width and height input fields specifically for controlling image dimensions, with options to maintain aspect ratio.
$this->add_control( 'image_size', [ 'label' => esc_html__( 'Image Dimensions', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::IMAGE_DIMENSIONS, 'default' => [ 'width' => '300', 'height' => '200', ], ] ); // Access: $settings['image_size']['width'], $settings['image_size']['height']
Responsive Controls
add_responsive_control()
The add_responsive_control() method automatically creates device-specific variants (desktop, tablet, mobile) of any
control, allowing different values for each breakpoint.
$this->add_responsive_control( 'content_align', [ 'label' => esc_html__( 'Alignment', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => 'Left', 'icon' => 'eicon-text-align-left' ], 'center' => [ 'title' => 'Center', 'icon' => 'eicon-text-align-center' ], 'right' => [ 'title' => 'Right', 'icon' => 'eicon-text-align-right' ], ], 'devices' => [ 'desktop', 'tablet', 'mobile' ], 'selectors' => [ '{{WRAPPER}} .content' => 'text-align: {{VALUE}};', ], ] ); // Creates: content_align, content_align_tablet, content_align_mobile
Responsive Selectors
Responsive selectors automatically generate device-specific CSS using Elementor's breakpoint system, applying styles wrapped in appropriate media queries.
$this->add_responsive_control( 'column_width', [ 'label' => esc_html__( 'Width', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 100 ] ], 'selectors' => [ '{{WRAPPER}} .col' => 'width: {{SIZE}}%;', ], ] );
Generated CSS:
─────────────────────────────────────
Desktop: .col { width: 33%; }
@media (max-width: 1024px) { .col { width: 50%; } }
@media (max-width: 767px) { .col { width: 100%; } }
Device-Specific Values
Access device-specific values in render methods using the _tablet and _mobile suffixes appended to control names,
with fallback logic for inheritance.
protected function render() { $settings = $this->get_settings_for_display(); // Desktop value $columns_desktop = $settings['columns']; // Tablet value (with fallback to desktop) $columns_tablet = $settings['columns_tablet'] ?: $columns_desktop; // Mobile value (with fallback chain) $columns_mobile = $settings['columns_mobile'] ?: $columns_tablet; // Dynamic output with device detection echo '<div data-cols="' . esc_attr( $columns_desktop ) . '" data-cols-tablet="' . esc_attr( $columns_tablet ) . '" data-cols-mobile="' . esc_attr( $columns_mobile ) . '">'; }
Responsive Conditions
Responsive conditions allow controls to be shown or hidden based on device preview mode or other control values, using
the conditions parameter.
$this->add_control( 'mobile_menu_icon', [ 'label' => esc_html__( 'Menu Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, 'condition' => [ 'show_mobile_menu' => 'yes', ], 'responsive' => [ 'mobile' => [ 'condition' => [ 'layout!' => 'inline', ], ], ], ] ); // Device-specific visibility $this->add_control( 'desktop_only_setting', [ 'label' => 'Desktop Only', 'type' => \Elementor\Controls_Manager::TEXT, 'devices' => [ 'desktop' ], // Only show for desktop ] );
Group Controls
add_group_control()
The add_group_control() method registers a predefined set of related controls as a single unit, automatically handling
naming prefixes, rendering, and CSS output.
$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), // Control type [ 'name' => 'title_typography', // Unique prefix 'label' => esc_html__( 'Typography', 'plugin-name' ), 'selector' => '{{WRAPPER}} .title', 'exclude' => [ 'font_style' ], // Exclude specific fields 'fields_options' => [ 'font_size' => [ 'default' => [ 'size' => 24 ] ], ], ] );
Group_Control_Typography
Group_Control_Typography bundles 10+ typography-related settings including font family, size, weight, transform, style, decoration, line-height, letter-spacing, and word-spacing.
$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'heading_typography', 'scheme' => \Elementor\Core\Schemes\Typography::TYPOGRAPHY_1, 'selector' => '{{WRAPPER}} h2', 'fields_options' => [ 'typography' => [ 'default' => 'yes' ], 'font_weight' => [ 'default' => '700' ], ], ] );
Group_Control_Text_Shadow
Group_Control_Text_Shadow provides horizontal offset, vertical offset, blur radius, and color controls to create text shadow effects on typography elements.
$this->add_group_control( \Elementor\Group_Control_Text_Shadow::get_type(), [ 'name' => 'title_shadow', 'selector' => '{{WRAPPER}} .title', 'fields_options' => [ 'text_shadow' => [ 'default' => [ 'horizontal' => 2, 'vertical' => 2, 'blur' => 4, 'color' => 'rgba(0,0,0,0.3)', ], ], ], ] );
Group_Control_Box_Shadow
Group_Control_Box_Shadow includes color, horizontal, vertical, blur, spread, and position (inset/outset) controls for comprehensive box-shadow styling.
$this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'card_shadow', 'label' => esc_html__( 'Box Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .card', 'fields_options' => [ 'box_shadow_type' => [ 'default' => 'yes' ], 'box_shadow' => [ 'default' => [ 'horizontal' => 0, 'vertical' => 10, 'blur' => 20, 'spread' => 0, 'color' => 'rgba(0,0,0,0.15)', ], ], ], ] );
Group_Control_Border
Group_Control_Border combines border type (solid, dashed, dotted, double, none), width controls for each side, and a color picker for unified border management.
$this->add_group_control( \Elementor\Group_Control_Border::get_type(), [ 'name' => 'box_border', 'label' => esc_html__( 'Border', 'plugin-name' ), 'selector' => '{{WRAPPER}} .box', 'fields_options' => [ 'border' => [ 'default' => 'solid' ], 'width' => [ 'default' => [ 'top' => '1', 'right' => '1', 'bottom' => '1', 'left' => '1', ], ], 'color' => [ 'default' => '#E0E0E0' ], ], ] );
Group_Control_Background
Group_Control_Background supports classic (solid color + image), gradient (linear/radial), video backgrounds, and slideshow with comprehensive positioning and overlay controls.
$this->add_group_control( \Elementor\Group_Control_Background::get_type(), [ 'name' => 'section_bg', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], // Remove image option 'selector' => '{{WRAPPER}} .section', 'fields_options' => [ 'background' => [ 'default' => 'gradient' ], 'color' => [ 'default' => '#6EC1E4' ], 'color_b' => [ 'default' => '#54595F' ], 'gradient_angle' => [ 'default' => [ 'size' => 180 ] ], ], ] );
Group_Control_Css_Filter
Group_Control_Css_Filter provides slider controls for blur, brightness, contrast, saturation, and hue-rotate CSS filter properties.
$this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'image_filters', 'selector' => '{{WRAPPER}} .image-wrapper img', ] ); // Hover state filters $this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'image_filters_hover', 'selector' => '{{WRAPPER}} .image-wrapper:hover img', ] );
Group_Control_Image_Size
Group_Control_Image_Size provides a dropdown of registered WordPress image sizes plus custom dimensions option for flexible image sizing.
$this->add_group_control( \Elementor\Group_Control_Image_Size::get_type(), [ 'name' => 'thumbnail', 'default' => 'medium', 'exclude' => [ 'custom' ], ] ); // In render(): $image_html = \Elementor\Group_Control_Image_Size::get_attachment_image_html( $settings, 'thumbnail', 'image' ); echo $image_html;
Creating Custom Group Controls
Custom group controls extend \Elementor\Group_Control_Base to bundle multiple related controls into a reusable unit
with shared logic and CSS output.
class Group_Control_Custom_Overlay extends \Elementor\Group_Control_Base { public static function get_type() { return 'custom-overlay'; } protected function init_fields() { $fields = []; $fields['color'] = [ 'label' => esc_html__( 'Overlay Color', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => [ '{{SELECTOR}}::before' => 'background-color: {{VALUE}};', ], ]; $fields['opacity'] = [ 'label' => esc_html__( 'Opacity', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 1, 'step' => 0.1 ] ], 'selectors' => [ '{{SELECTOR}}::before' => 'opacity: {{SIZE}};', ], ]; return $fields; } protected function get_default_options() { return [ 'popover' => [ 'starter_name' => 'overlay', 'starter_title' => esc_html__( 'Overlay', 'plugin-name' ), ], ]; } } // Register: Plugin::instance()->controls_manager->add_group_control( 'custom-overlay', new Group_Control_Custom_Overlay() );
Repeater Control
Repeater Class
The Repeater class creates dynamic, sortable lists of items where each item contains a set of controls, enabling users to add, remove, and reorder entries.
$repeater = new \Elementor\Repeater(); $repeater->add_control( 'item_title', [ 'label' => esc_html__( 'Title', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__( 'Item Title', 'plugin-name' ), ] ); $this->add_control( 'items_list', [ 'label' => esc_html__( 'Items', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'default' => [ [ 'item_title' => 'Item #1' ], [ 'item_title' => 'Item #2' ], ], 'title_field' => '{{{ item_title }}}', ] );
┌─────────────────────────────────┐
│ [≡] Item #1 ✕ │
├─────────────────────────────────┤
│ [≡] Item #2 ✕ │
├─────────────────────────────────┤
│ [≡] Item #3 ✕ │
└─────────────────────────────────┘
[+ Add Item]
Adding Repeater Controls
Repeater controls support all standard control types including text, media, icons, selects, and even group controls, added via the Repeater instance before registration.
$repeater = new \Elementor\Repeater(); $repeater->add_control( 'tab_title', [ 'label' => esc_html__( 'Tab Title', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => 'Tab Title', ] ); $repeater->add_control( 'tab_icon', [ 'label' => esc_html__( 'Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, ] ); $repeater->add_control( 'tab_content', [ 'label' => esc_html__( 'Content', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::WYSIWYG, ] ); // Add group control to repeater $repeater->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'tab_typography', 'selector' => '{{WRAPPER}} {{CURRENT_ITEM}} .tab-text', ] );
Rendering Repeater Data
Render repeater data by looping through the items array, using {{CURRENT_ITEM}} selector placeholder for item-specific
CSS targeting.
protected function render() { $settings = $this->get_settings_for_display(); if ( empty( $settings['items_list'] ) ) { return; } echo '<div class="items-wrapper">'; foreach ( $settings['items_list'] as $index => $item ) { $item_class = 'elementor-repeater-item-' . esc_attr( $item['_id'] ); ?> <div class="item <?php echo $item_class; ?>"> <h3><?php echo esc_html( $item['item_title'] ); ?></h3> <p><?php echo wp_kses_post( $item['item_content'] ); ?></p> <?php \Elementor\Icons_Manager::render_icon( $item['item_icon'] ); ?> </div> <?php } echo '</div>'; }
Repeater with Conditions
Conditions within repeaters show/hide controls based on other repeater field values, using standard condition syntax targeting sibling controls.
$repeater = new \Elementor\Repeater(); $repeater->add_control( 'link_type', [ 'label' => esc_html__( 'Link Type', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'none' => 'None', 'url' => 'URL', 'page' => 'Page', ], 'default' => 'none', ] ); $repeater->add_control( 'link_url', [ 'label' => esc_html__( 'URL', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::URL, 'condition' => [ 'link_type' => 'url', // Only show when URL selected ], ] ); $repeater->add_control( 'link_page', [ 'label' => esc_html__( 'Page', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SELECT2, 'options' => $this->get_pages_list(), 'condition' => [ 'link_type' => 'page', // Only show when Page selected ], ] );
Nested Repeaters
Nested repeaters (repeaters within repeaters) require careful implementation and are generally handled through workarounds since Elementor doesn't natively support deep nesting.
// Primary Repeater (Parent) $parent_repeater = new \Elementor\Repeater(); $parent_repeater->add_control( 'section_title', [ 'label' => 'Section Title', 'type' => \Elementor\Controls_Manager::TEXT, ] ); // Child items as TEXTAREA with JSON (workaround) $parent_repeater->add_control( 'child_items', [ 'label' => 'Sub Items', 'type' => \Elementor\Controls_Manager::TEXTAREA, 'description' => 'Enter items, one per line', ] ); // Alternative: Use GALLERY for nested images $parent_repeater->add_control( 'nested_gallery', [ 'label' => 'Images', 'type' => \Elementor\Controls_Manager::GALLERY, ] ); $this->add_control( 'sections', [ 'label' => 'Sections', 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $parent_repeater->get_controls(), ] ); // Render nested structure protected function render() { foreach ( $settings['sections'] as $section ) { echo '<div class="section">'; echo '<h2>' . esc_html( $section['section_title'] ) . '</h2>'; // Parse child items from textarea $children = explode( "\n", $section['child_items'] ); echo '<ul>'; foreach ( $children as $child ) { echo '<li>' . esc_html( trim( $child ) ) . '</li>'; } echo '</ul></div>'; } }
┌─ Parent Repeater ────────────────┐
│ ┌─ Item 1 ───────────────────┐ │
│ │ Title: [Section A ] │ │
│ │ ┌─ Nested Items ─────────┐ │ │
│ │ │ • Sub-item 1 │ │ │
│ │ │ • Sub-item 2 │ │ │
│ │ └────────────────────────┘ │ │
│ └────────────────────────────┘ │
│ ┌─ Item 2 ───────────────────┐ │
│ │ ... │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
Quick Reference Chart
┌─────────────────────────────────────────────────────────────────┐
│ ELEMENTOR CONTROLS HIERARCHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─ Basic Controls ─────┐ ┌─ Advanced Controls ─────────────┐ │
│ │ • TEXT │ │ • MEDIA (image/video selector) │ │
│ │ • TEXTAREA │ │ • GALLERY (multi-image) │ │
│ │ • NUMBER │ │ • ICONS (icon picker) │ │
│ │ • SELECT │ │ • SLIDER (range input) │ │
│ │ • SWITCHER │ │ • DIMENSIONS (margin/padding) │ │
│ │ • COLOR │ │ • REPEATER (dynamic lists) │ │
│ └──────────────────────┘ └─────────────────────────────────┘ │
│ │
│ ┌─ Group Controls ──────────────────────────────────────────┐ │
│ │ Typography │ Background │ Border │ Box Shadow │ CSS Filter│ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Responsive Layer ────────────────────────────────────────┐ │
│ │ Desktop Tablet Mobile │ │
│ │ (>1024px) (768-1024px) (<768px) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘