Back to Articles
24 min read

Optimizing Elementor Widgets: Rendering Pipelines, Frontend Handlers, and Dynamic Styling

Complete the custom widget lifecycle by mastering the rendering and execution phases. This technical deep dive explores dual-stack rendering (`render()` vs `content_template()`), robust attribute management, and frontend JavaScript integration via the `elementorFrontend` object. We analyze critical optimization patterns—from dependency injection to the `{{WRAPPER}}` CSS selector scope—ensuring your components are not just functional, but performant and native-feeling within the Elementor editor.

Tabs and Sections

TABS_WRAPPER

The TABS_WRAPPER constant defines the wrapper control type that creates a tabbed interface within the Elementor panel, allowing you to organize sections of controls into logical groups that users can switch between.

$this->start_controls_tabs( 'style_tabs' ); $this->start_controls_tab( 'normal_tab', [ 'label' => 'Normal' ] ); // Normal state controls $this->end_controls_tab(); $this->start_controls_tab( 'hover_tab', [ 'label' => 'Hover' ] ); // Hover state controls $this->end_controls_tab(); $this->end_controls_tabs();

TAB_CONTENT

TAB_CONTENT is one of Elementor's built-in panel tabs (the first tab) where you place primary content-related controls like text fields, media selectors, and repeaters that define what the widget displays.

$this->start_controls_section( 'content_section', [ 'label' => __( 'Content', 'plugin-name' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // Default tab ] );
┌─────────────────────────────────────────┐
│  [Content]   Style    Advanced          │
│  ─────────                              │
│  ┌───────────────────────────────────┐  │
│  │  Title: [__________________]      │  │
│  │  Description: [____________]      │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

TAB_STYLE

TAB_STYLE is the second panel tab dedicated to visual styling controls like typography, colors, spacing, and borders that determine how the widget looks without affecting its content.

$this->start_controls_section( 'style_section', [ 'label' => __( 'Style', 'plugin-name' ), 'tab' => \Elementor\Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'text_color', [ 'label' => __( 'Color', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .title' => 'color: {{VALUE}}' ], ]);

TAB_ADVANCED

TAB_ADVANCED is the third panel tab containing advanced settings like custom CSS, motion effects, responsive visibility, and custom attributes—mostly inherited from Elementor's base widget class.

$this->start_controls_section( 'advanced_section', [ 'label' => __( 'Custom Settings', 'plugin-name' ), 'tab' => \Elementor\Controls_Manager::TAB_ADVANCED, ] ); // Custom advanced controls here $this->end_controls_section();

Tabs in Controls

Inner control tabs (not panel tabs) group related control states like Normal/Hover/Active within a single section, enabling users to define different styles for different element states.

$this->start_controls_tabs( 'button_style_tabs' ); $this->start_controls_tab( 'tab_button_normal', [ 'label' => __( 'Normal', 'plugin-name' ), ]); $this->add_control( 'button_color', [...] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => __( 'Hover', 'plugin-name' ), ]); $this->add_control( 'button_hover_color', [...] ); $this->end_controls_tab(); $this->end_controls_tabs();
┌─────────────────────────────────┐
│ Button Style                    │
│ ┌──────────┬──────────┐         │
│ │  Normal  │  Hover   │         │
│ └──────────┴──────────┘         │
│   Color: [████████] #333        │
│   Background: [████] #fff       │
└─────────────────────────────────┘

Custom Section Injection

Section injection allows you to add custom controls or sections to existing Elementor widgets without modifying their source code, using action hooks that fire during widget registration.

add_action( 'elementor/element/button/section_style/after_section_end', function( $element, $args ) { $element->start_controls_section( 'custom_section', [ 'label' => __( 'Custom Options', 'plugin-name' ), 'tab' => \Elementor\Controls_Manager::TAB_STYLE, ]); $element->add_control( 'custom_opacity', [ 'label' => __( 'Opacity', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, ]); $element->end_controls_section(); }, 10, 2 );

Section Conditions

Section conditions allow you to show or hide entire control sections based on the values of other controls, creating dynamic panel interfaces that respond to user selections.

$this->start_controls_section( 'icon_section', [ 'label' => __( 'Icon Settings', 'plugin-name' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, 'condition' => [ 'show_icon' => 'yes', // Simple condition 'layout_type!' => 'minimal', // Not equal 'style' => [ 'style1', 'style2' ], // In array ], ]);

Widget Rendering

render() Method

The render() method is the core PHP function that outputs your widget's frontend HTML; it's called during page load and AJAX preview updates, making it the primary rendering mechanism for production output.

protected function render() { $settings = $this->get_settings_for_display(); ?> <div class="my-widget"> <h2><?php echo esc_html( $settings['title'] ); ?></h2> <p><?php echo wp_kses_post( $settings['description'] ); ?></p> </div> <?php }

content_template() Method

The content_template() method provides a JavaScript/Underscore.js template that enables instant live preview in the editor without server round-trips, significantly improving the editing experience.

protected function content_template() { ?> <# var title = settings.title; var description = settings.description; #> <div class="my-widget"> <h2>{{{ title }}}</h2> <p>{{{ description }}}</p> </div> <?php }

PHP Rendering vs JS Rendering

PHP rendering (render()) executes on the server for frontend display and editor preview via AJAX, while JS rendering (content_template()) runs client-side for instant editor updates—both should produce identical output.

┌─────────────────────────────────────────────────────────┐
│                    RENDERING FLOW                       │
├─────────────────────────────────────────────────────────┤
│  FRONTEND PAGE LOAD                                     │
│  ──────────────────                                     │
│  PHP render() ──► HTML Output ──► Browser               │
│                                                         │
│  EDITOR PREVIEW (Initial)                               │
│  ────────────────────────                               │
│  PHP render() ──► AJAX ──► iframe                       │
│                                                         │
│  EDITOR PREVIEW (Live Changes)                          │
│  ─────────────────────────────                          │
│  JS content_template() ──► Instant Update               │
└─────────────────────────────────────────────────────────┘

get_settings_for_display()

This method retrieves all widget settings with dynamic tags parsed and default values applied, returning a clean array ready for rendering—always use this instead of directly accessing settings.

protected function render() { // ✅ Correct - parses dynamic tags, applies defaults $settings = $this->get_settings_for_display(); // Access specific setting with dynamic tag support $title = $this->get_settings_for_display( 'title' ); // ❌ Avoid - raw settings without processing // $raw = $this->get_settings(); echo '<h2>' . esc_html( $settings['title'] ) . '</h2>'; }

Escaping Output

Proper output escaping is critical for security; use WordPress escaping functions (esc_html, esc_attr, esc_url, wp_kses_post) to prevent XSS attacks when rendering user-controlled content.

protected function render() { $settings = $this->get_settings_for_display(); ?> <div class="widget"> <!-- Text content --> <h2><?php echo esc_html( $settings['title'] ); ?></h2> <!-- HTML content (limited tags) --> <div><?php echo wp_kses_post( $settings['content'] ); ?></div> <!-- URL --> <a href="<?php echo esc_url( $settings['link']['url'] ); ?>"> <!-- Attribute --> <div data-id="<?php echo esc_attr( $settings['id'] ); ?>"> </div> <?php }

Render Attributes

Render attributes provide a structured way to build HTML element attributes dynamically, allowing multiple code locations to add classes, IDs, and data attributes that get merged and output together.

protected function render() { // Build attributes programmatically $this->add_render_attribute( 'wrapper', [ 'class' => [ 'my-widget', 'theme-' . $settings['theme'] ], 'id' => 'widget-' . $this->get_id(), 'data-animation' => $settings['animation'], ]); ?> <div <?php $this->print_render_attribute_string( 'wrapper' ); ?>> <!-- Content --> </div> <?php }

add_render_attribute()

This method adds attributes to a named element key, supporting strings, arrays, and multiple calls that merge values—essential for building complex attributes programmatically.

// Single attribute $this->add_render_attribute( 'button', 'class', 'btn' ); // Multiple attributes at once $this->add_render_attribute( 'button', [ 'class' => 'btn-primary', 'role' => 'button', 'href' => $settings['link']['url'], ]); // Append to existing (classes merge) $this->add_render_attribute( 'button', 'class', 'btn-large' ); // Result: class="btn btn-primary btn-large" role="button" href="..."

get_render_attribute_string()

Returns all attributes for a named element as an escaped string ready for insertion into HTML, useful when you need to store or manipulate the string before output.

protected function render() { $this->add_render_attribute( 'link', [ 'class' => 'widget-link', 'href' => $settings['url'], 'target' => $settings['target'] ? '_blank' : '_self', ]); // Get as string (already escaped) $link_attrs = $this->get_render_attribute_string( 'link' ); // Use in sprintf or concatenation printf( '<a %s>%s</a>', $link_attrs, esc_html( $settings['text'] ) ); }

Directly echoes the escaped attribute string to output, the most common way to apply render attributes within HTML templates in the render() method.

protected function render() { $this->add_render_attribute( 'container', 'class', 'my-container' ); $this->add_render_attribute( 'title', 'class', 'my-title' ); ?> <div <?php $this->print_render_attribute_string( 'container' ); ?>> <h2 <?php $this->print_render_attribute_string( 'title' ); ?>> <?php echo esc_html( $settings['title'] ); ?> </h2> </div> <?php } <!-- Output: <div class="my-container"><h2 class="my-title">...</h2></div> -->

add_inline_editing_attributes()

This method adds necessary data attributes that enable Elementor's inline editing feature, allowing users to click and edit text directly on the preview without using the panel.

protected function render() { $settings = $this->get_settings_for_display(); // Add inline editing support $this->add_inline_editing_attributes( 'title', 'none' ); // No formatting $this->add_inline_editing_attributes( 'subtitle', 'basic' ); // Bold, italic $this->add_inline_editing_attributes( 'content', 'advanced' );// Full editor ?> <h1 <?php $this->print_render_attribute_string( 'title' ); ?>> <?php echo esc_html( $settings['title'] ); ?> </h1> <div <?php $this->print_render_attribute_string( 'content' ); ?>> <?php echo wp_kses_post( $settings['content'] ); ?> </div> <?php }

Inline Editing Support

Full inline editing requires coordinating PHP render attributes with JS content_template selectors, using the elementor-inline-editing class and matching data attributes in both methods.

// PHP render() $this->add_inline_editing_attributes( 'title', 'basic' ); ?> <h2 <?php $this->print_render_attribute_string( 'title' ); ?>> <?php echo $settings['title']; ?> </h2> // JS content_template() <# view.addInlineEditingAttributes( 'title', 'basic' ); var titleAttrs = view.getRenderAttributeString( 'title' ); #> <h2 {{{ titleAttrs }}}>{{{ settings.title }}}</h2>
┌────────────────────────────────────────┐
│  Inline Editing Modes                  │
├────────────────────────────────────────┤
│  'none'     → Plain text, no toolbar   │
│  'basic'    → Bold, Italic, Underline  │
│  'advanced' → Full TinyMCE editor      │
└────────────────────────────────────────┘

Widget Styles

Widget CSS

Widget styles can be defined via control selectors that generate dynamic CSS, or through registered external stylesheets that provide base styling for the widget structure.

// Dynamic CSS via control selectors $this->add_control( 'title_color', [ 'label' => __( 'Title Color', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .widget-title' => 'color: {{VALUE}};', '{{WRAPPER}} .widget-icon' => 'fill: {{VALUE}};', ], ]); // External stylesheet wp_register_style( 'my-widget-css', plugins_url( 'css/widget.css', __FILE__ ) );

Selector Placeholders

Selector placeholders are dynamic tokens in CSS selector strings that Elementor replaces with actual values at render time, enabling responsive, scoped, and setting-dependent styles.

'selectors' => [ // {{WRAPPER}} = unique widget wrapper selector '{{WRAPPER}} .title' => 'color: {{VALUE}};', // {{VALUE}} = control's current value '{{WRAPPER}}' => 'background: {{VALUE}};', // {{SIZE}}{{UNIT}} = for slider controls '{{WRAPPER}} .box' => 'width: {{SIZE}}{{UNIT}};', // {{URL}} = for media/image controls '{{WRAPPER}}' => 'background-image: url({{URL}});', ]

{{WRAPPER}} Selector

The {{WRAPPER}} placeholder resolves to the unique CSS selector for each widget instance (.elementor-element-{id}), ensuring styles only affect that specific widget without conflicts.

$this->add_control( 'bg_color', [ 'selectors' => [ // Targets only THIS widget instance '{{WRAPPER}}' => 'background-color: {{VALUE}};', '{{WRAPPER}} .inner' => 'border-color: {{VALUE}};', '{{WRAPPER}}:hover .title' => 'color: {{VALUE}};', ], ]); // Rendered CSS: // .elementor-element-a1b2c3d { background-color: #ff0000; } // .elementor-element-a1b2c3d .inner { border-color: #ff0000; }

Dynamic Selectors

Dynamic selectors allow building CSS rules that change based on control values, using multiple value placeholders and conditional logic for complex styling scenarios.

// Multiple value placeholders $this->add_control( 'border', [ 'type' => \Elementor\Controls_Manager::DIMENSIONS, 'selectors' => [ '{{WRAPPER}} .box' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ]); // Combined selectors for hover states $this->add_control( 'text_color', [ 'selectors' => [ '{{WRAPPER}} .text, {{WRAPPER}} .icon' => 'color: {{VALUE}};', '{{WRAPPER}}:hover .text' => 'color: {{VALUE}};', ], ]);

CSS Variables

CSS custom properties can be set via Elementor selectors, enabling cascading styles, JavaScript access, and reduced CSS output when multiple elements share values.

// Set CSS variables via selectors $this->add_control( 'primary_color', [ 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => '--widget-primary: {{VALUE}};', ], ]); $this->add_control( 'spacing', [ 'type' => \Elementor\Controls_Manager::SLIDER, 'selectors' => [ '{{WRAPPER}}' => '--widget-spacing: {{SIZE}}{{UNIT}};', ], ]); /* widget.css - uses the variables */ .widget-title { color: var(--widget-primary); } .widget-content { padding: var(--widget-spacing); }

Widget Scripts

get_script_depends()

This method declares JavaScript file handles that must be loaded when the widget is present, returning an array of registered script handles that Elementor loads conditionally.

class My_Widget extends \Elementor\Widget_Base { public function get_script_depends() { return [ 'jquery', 'swiper', 'my-widget-script' ]; } public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); wp_register_script( 'my-widget-script', plugins_url( 'js/widget.js', __FILE__ ), [ 'jquery', 'elementor-frontend' ], '1.0.0', true ); } }

get_style_depends()

Similar to scripts, this method returns an array of registered stylesheet handles that Elementor loads only when the widget is used on a page.

public function get_style_depends() { return [ 'swiper-css', 'my-widget-styles' ]; } public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); wp_register_style( 'my-widget-styles', plugins_url( 'css/widget.css', __FILE__ ), [], '1.0.0' ); }

Frontend JavaScript

Frontend JS for widgets typically initializes after the DOM is ready and handles events, animations, and interactions—structured as a handler class that Elementor instantiates for each widget instance.

// js/widget-handler.js class MyWidgetHandler extends elementorModules.frontend.handlers.Base { getDefaultSettings() { return { selectors: { wrapper: '.my-widget', button: '.my-button', }, }; } getDefaultElements() { const selectors = this.getSettings('selectors'); return { $wrapper: this.$element.find(selectors.wrapper), $button: this.$element.find(selectors.button), }; } bindEvents() { this.elements.$button.on('click', this.onButtonClick.bind(this)); } onButtonClick(e) { this.elements.$wrapper.toggleClass('active'); } }

Handler Initialization

Widget handlers must be registered with elementorFrontend to initialize when widgets appear on the page or in the editor preview, using the frontend/element_ready hook.

jQuery(window).on('elementor/frontend/init', function() { // Register handler for specific widget elementorFrontend.hooks.addAction( 'frontend/element_ready/my-widget.default', // widget-name.skin function($element) { new MyWidgetHandler({ $element: $element }); } ); // Or using elementsHandler elementorFrontend.elementsHandler.attachHandler( 'my-widget', MyWidgetHandler ); });

elementorFrontend.hooks

The frontend hooks system provides action and filter hooks similar to WordPress, allowing you to tap into Elementor's frontend lifecycle for initialization, responsive changes, and more.

jQuery(window).on('elementor/frontend/init', function() { // Element ready hook (most common) elementorFrontend.hooks.addAction( 'frontend/element_ready/widget', // All widgets function($element) { } ); // Specific widget ready elementorFrontend.hooks.addAction( 'frontend/element_ready/my-widget.default', function($element) { } ); // Global init elementorFrontend.hooks.addAction( 'frontend/init', function() { } ); });
┌─────────────────────────────────────────────────┐
│  FRONTEND HOOKS LIFECYCLE                       │
├─────────────────────────────────────────────────┤
│  1. frontend/init                               │
│  2. frontend/element_ready/global               │
│  3. frontend/element_ready/widget               │
│  4. frontend/element_ready/{name}.{skin}        │
└─────────────────────────────────────────────────┘

Widget Handler Class

The handler class pattern extends Elementor's base handler, providing structure for DOM caching, event binding, responsive callbacks, and cleanup in a reusable, testable format.

class AdvancedSlider extends elementorModules.frontend.handlers.Base { getDefaultSettings() { return { selectors: { slider: '.slider', slide: '.slide' } }; } getDefaultElements() { return { $slider: this.$element.find(this.getSettings('selectors.slider')), }; } bindEvents() { // Respond to Elementor's responsive changes elementorFrontend.elements.$window.on( 'resize', this.onWindowResize.bind(this) ); } onInit() { super.onInit(); this.initSlider(); } initSlider() { this.swiper = new Swiper(this.elements.$slider[0], { slidesPerView: this.getElementSettings('slides_per_view'), }); } }

Frontend Config

Pass PHP data to frontend JavaScript using wp_localize_script or Elementor's settings system, providing URLs, translations, and dynamic configuration to your widget handlers.

// In widget constructor or enqueue hook wp_localize_script( 'my-widget-script', 'myWidgetConfig', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'my_widget_nonce' ), 'i18n' => [ 'loading' => __( 'Loading...', 'plugin-name' ), 'error' => __( 'Error occurred', 'plugin-name' ), ], ]); // In JavaScript console.log(myWidgetConfig.ajaxUrl); console.log(myWidgetConfig.i18n.loading);

Widget Categories

Creating Custom Categories

Custom categories group your widgets separately from built-in ones, created by hooking into elementor/elements/categories_registered and using the add_category method.

add_action( 'elementor/elements/categories_registered', function( $elements_manager ) { $elements_manager->add_category( 'my-theme-widgets', // Category slug [ 'title' => __( 'My Theme Widgets', 'plugin-name' ), 'icon' => 'fa fa-plug', // Category icon ] ); // Add at specific position $elements_manager->add_category( 'premium-widgets', [ 'title' => __( 'Premium Widgets', 'plugin-name' ), 'icon' => 'eicon-star', ], 1 // Position (0 = first) ); });

Category Icons

Category icons appear in the Elementor panel sidebar and can use Elementor's built-in eicon-* icons, Font Awesome classes, or custom icon fonts registered with the editor.

$elements_manager->add_category( 'my-category', [ 'title' => 'My Category', 'icon' => 'eicon-gallery-grid', // Elementor icon // OR 'icon' => 'fa fa-rocket', // Font Awesome // OR 'icon' => 'my-custom-icon-class', // Custom icon font ]);
┌─────────────────────────────────────┐
│  Common Elementor Icons (eicon-)    │
├─────────────────────────────────────┤
│  eicon-apps        eicon-gallery    │
│  eicon-star        eicon-settings   │
│  eicon-tools       eicon-plug       │
│  eicon-code        eicon-shortcode  │
└─────────────────────────────────────┘

Category Priority

Category position is controlled by the third parameter of add_category(), where lower numbers appear first—useful for ensuring your custom widgets are prominently displayed.

add_action( 'elementor/elements/categories_registered', function( $elements_manager ) { // Position 0 = First category $elements_manager->add_category( 'featured-widgets', [ 'title' => 'Featured', ], 0 ); // Position 1 = Second $elements_manager->add_category( 'theme-widgets', [ 'title' => 'Theme', ], 1 ); // No position = Added at end $elements_manager->add_category( 'misc-widgets', [ 'title' => 'Miscellaneous', ]); });

Multiple Categories

Widgets can appear in multiple categories by returning an array from get_categories(), with the first category being the primary one where the widget is officially listed.

class My_Widget extends \Elementor\Widget_Base { public function get_categories() { return [ 'my-theme-widgets', 'general', 'basic' ]; } // Widget appears in all three categories // First one ('my-theme-widgets') is primary }

Widget Icons

Elementor Icon Library

Elementor's built-in icon library (eicon-*) provides consistent, optimized icons for the editor interface—use these for widget icons to match Elementor's visual style.

public function get_icon() { return 'eicon-slider-push'; // Built-in Elementor icon } // Common widget icons: // eicon-button, eicon-image, eicon-video-camera // eicon-slider-album, eicon-carousel, eicon-gallery-grid // eicon-posts-grid, eicon-text, eicon-heading // eicon-icon-box, eicon-info-box, eicon-accordion

Font Awesome Icons

Font Awesome icons can be used for widget icons when the built-in Elementor icons don't have what you need, using the standard fa fa-* or fas fa-* class syntax.

public function get_icon() { return 'fa fa-chart-bar'; // FA 4 style // OR return 'fas fa-chart-bar'; // FA 5 Solid return 'far fa-chart-bar'; // FA 5 Regular return 'fab fa-wordpress'; // FA 5 Brands }

Custom Icon Fonts

Register custom icon fonts with Elementor's icon system to use your own icons throughout widgets and the editor, enabling branded icon sets.

add_filter( 'elementor/icons_manager/additional_tabs', function( $tabs ) { $tabs['my-icons'] = [ 'name' => 'my-icons', 'label' => __( 'My Icons', 'plugin-name' ), 'prefix' => 'my-icon-', 'displayPrefix' => 'my-icon', 'labelIcon' => 'my-icon-star', 'ver' => '1.0.0', 'fetchJson' => plugins_url( 'icons/icons.json', __FILE__ ), 'url' => plugins_url( 'icons/style.css', __FILE__ ), 'native' => true, ]; return $tabs; });

SVG Icons

SVG icons can be used by enabling SVG uploads in Elementor settings, registering SVGs as icon library entries, or inlining SVG code directly in widget render methods.

// Enable SVG uploads (careful with security) add_filter( 'elementor/icons_manager/native', function( $icons ) { $icons['svg'] = [ 'name' => 'svg', 'label' => 'SVG Upload', 'native' => true, ]; return $icons; }); // Or inline SVG in render method protected function render() { ?> <div class="widget-icon"> <svg viewBox="0 0 24 24" width="24" height="24"> <path d="M12 2L2 7l10 5 10-5-10-5z"/> </svg> </div> <?php }

Widget Dependencies

Script Dependencies

Declare all JavaScript dependencies through get_script_depends() and ensure parent scripts are registered before your widget script to maintain proper load order.

public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); // Register third-party library wp_register_script( 'swiper', 'https://cdn.../swiper.min.js', [], '8.0' ); // Register widget script with dependencies wp_register_script( 'my-slider-widget', plugins_url( 'js/slider.js', __FILE__ ), [ 'jquery', 'swiper', 'elementor-frontend' ], // Dependencies '1.0.0', true ); } public function get_script_depends() { return [ 'swiper', 'my-slider-widget' ]; }

Style Dependencies

CSS dependencies follow the same pattern, using get_style_depends() to ensure stylesheets load in the correct order with proper dependency chains.

public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); wp_register_style( 'swiper-css', 'https://cdn.../swiper.min.css' ); wp_register_style( 'my-slider-styles', plugins_url( 'css/slider.css', __FILE__ ), [ 'swiper-css', 'elementor-frontend' ], // Load after these '1.0.0' ); } public function get_style_depends() { return [ 'swiper-css', 'my-slider-styles' ]; }

Conditional Loading

Elementor only loads scripts/styles declared in dependency methods when the widget is actually used on the page, but you can add further conditional logic for specific settings.

public function get_script_depends() { $scripts = [ 'my-widget-base' ]; // Check if widget uses certain features $settings = $this->get_settings(); if ( ! empty( $settings['enable_lightbox'] ) ) { $scripts[] = 'lightbox-script'; } if ( $settings['animation_type'] === 'gsap' ) { $scripts[] = 'gsap'; } return $scripts; }
┌─────────────────────────────────────────────────────┐
│  DEPENDENCY LOADING FLOW                            │
├─────────────────────────────────────────────────────┤
│  Page Load                                          │
│      │                                              │
│      ▼                                              │
│  Has Widget? ──No──► Don't load assets              │
│      │                                              │
│     Yes                                             │
│      │                                              │
│      ▼                                              │
│  get_script_depends() ──► Load declared scripts     │
│  get_style_depends()  ──► Load declared styles      │
└─────────────────────────────────────────────────────┘

Performance Optimization

Optimize widget performance by lazy-loading heavy assets, using async/defer attributes, conditionally loading based on viewport visibility, and minimizing DOM operations.

// Register scripts with defer wp_register_script( 'heavy-lib', $url, [], '1.0', [ 'in_footer' => true, 'strategy' => 'defer', // WP 6.3+ ]); // Lazy load images protected function render() { ?> <img src="<?php echo esc_url( $settings['image']['url'] ); ?>" loading="lazy" decoding="async"> <?php }
// Intersection Observer for lazy init class MyWidget extends elementorModules.frontend.handlers.Base { onInit() { super.onInit(); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.initHeavyFeature(); observer.unobserve(entry.target); } }); }); observer.observe(this.$element[0]); } }

Quick Reference Card

┌─────────────────────────────────────────────────────────────────────┐
│                    ELEMENTOR WIDGET CHEATSHEET                      │
├─────────────────────────────────────────────────────────────────────┤
│  TABS                                                               │
│  Controls_Manager::TAB_CONTENT  │  TAB_STYLE  │  TAB_ADVANCED       │
├─────────────────────────────────────────────────────────────────────┤
│  RENDERING                                                          │
│  render()              → PHP/Server output                          │
│  content_template()    → JS/Editor preview                          │
│  get_settings_for_display() → Get processed settings                │
├─────────────────────────────────────────────────────────────────────┤
│  ATTRIBUTES                                                         │
│  add_render_attribute( 'key', 'attr', 'value' )                     │
│  print_render_attribute_string( 'key' )                             │
│  add_inline_editing_attributes( 'key', 'none|basic|advanced' )      │
├─────────────────────────────────────────────────────────────────────┤
│  SELECTORS                                                          │
│  {{WRAPPER}}           → Widget unique selector                     │
│  {{VALUE}}             → Control value                              │
│  {{SIZE}}{{UNIT}}      → Slider value + unit                        │
├─────────────────────────────────────────────────────────────────────┤
│  DEPENDENCIES                                                       │
│  get_script_depends()  → Return ['script-handle']                   │
│  get_style_depends()   → Return ['style-handle']                    │
├─────────────────────────────────────────────────────────────────────┤
│  FRONTEND JS HOOK                                                   │
│  elementorFrontend.hooks.addAction(                                 │
│      'frontend/element_ready/widget-name.default', fn               │
│  );                                                                 │
└─────────────────────────────────────────────────────────────────────┘