WP Manifestindependent plugin directory
manifest / builders / elementor-widgets

Elementor Widgets

WordPress Plugin: Testimonial plugin focused on custom Elementor widget development.

by Abu Nesar · github.com/rmdabunesar/elementor-widgets · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/rmdabunesar/elementor-widgets/archive/refs/heads/main.zip

Elementor Custom Widget Development Guide

Requirements: WordPress 6.0+, PHP 8.0+, Elementor (free)


1. Project Structure

elementor-widgets/
├── elementor-widgets.php
├── assets/
│   ├── css/testimonial.min.css
│   └── js/testimonial.min.js
└── widgets/
    └── class-testimonial-widget.php   ← one file per widget

One widget class per file. File name: class-{widget-slug}.php.


2. Plugin Bootstrap

Singleton class wiring all hooks in one place.

if ( ! defined( 'ABSPATH' ) ) { exit; }

define( 'EW_VERSION',    '1.0.0' );
define( 'EW_PATH',       plugin_dir_path( __FILE__ ) );
define( 'EW_ASSETS_URL', plugin_dir_url( __FILE__ ) . 'assets/' );

final class Elementor_Widgets {

    private static ?self $instance = null;

    public static function instance(): self {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        add_action( 'elementor/elements/categories_registered', [ $this, 'register_widget_category' ] );
        add_action( 'elementor/widgets/register',               [ $this, 'register_widgets' ] );
        add_action( 'wp_enqueue_scripts',                       [ $this, 'enqueue_frontend_styles' ] );
        add_action( 'wp_enqueue_scripts',                       [ $this, 'register_frontend_scripts' ] );
        add_action( 'init',                                     [ $this, 'load_textdomain' ] );
    }
}

Elementor_Widgets::instance();

3. Register Category, Widget & Assets

// Custom panel category
public function register_widget_category( \Elementor\Elements_Manager $em ): void {
    $em->add_category( 'ew-widgets', [
        'title' => esc_html__( 'My Widgets', 'elementor-widgets' ),
        'icon'  => 'fa fa-plug',
    ] );
}

// Load and register each widget
public function register_widgets( \Elementor\Widgets_Manager $wm ): void {
    require_once EW_PATH . 'widgets/class-testimonial-widget.php';
    $wm->register( new \ElementorWidgets\Widgets\Testimonial_Widget() );
}

// Enqueue styles globally; register scripts only (Elementor loads on demand)
public function enqueue_frontend_styles(): void {
    wp_enqueue_style( 'ew-testimonial-style', EW_ASSETS_URL . 'css/testimonial.min.css', [], EW_VERSION );
}

public function register_frontend_scripts(): void {
    wp_register_script( 'ew-testimonial-script', EW_ASSETS_URL . 'js/testimonial.min.js', [ 'jquery' ], EW_VERSION, true );
}

4. Widget Class Skeleton

namespace ElementorWidgets\Widgets;

use Elementor\Widget_Base;
use Elementor\Controls_Manager;

if ( ! defined( 'ABSPATH' ) ) { exit; }

class Testimonial_Widget extends Widget_Base {

    public function get_name(): string      { return 'ew_testimonial'; }
    public function get_title(): string     { return esc_html__( 'Testimonial Showcase', 'elementor-widgets' ); }
    public function get_icon(): string      { return 'eicon-testimonial-carousel'; }
    public function get_categories(): array { return [ 'ew-widgets' ]; }
    public function get_keywords(): array   { return [ 'testimonial', 'review', 'client' ]; }

    public function get_style_depends(): array  { return [ 'swiper', 'ew-testimonial-style' ]; }
    public function get_script_depends(): array { return [ 'swiper', 'ew-testimonial-script' ]; }

    protected function register_controls(): void {
        $this->register_content_controls();
        $this->register_style_controls();
    }

    protected function render(): void { /* see §6 */ }
}

Identity methods:

Method Purpose
get_name() Unique snake_case slug
get_title() Panel label — wrap in esc_html__()
get_icon() eicon-* or fa fa-*
get_categories() Must match a registered category slug
get_style_depends() / get_script_depends() Handles Elementor enqueues per-page

5. Controls — Content Tab

Wrap every group in a section, then add controls.

private function register_content_controls(): void {

    $this->start_controls_section( 'section_layout', [
        'label' => esc_html__( 'Layout', 'elementor-widgets' ),
        'tab'   => Controls_Manager::TAB_CONTENT,
    ] );

    // SELECT
    $this->add_control( 'layout', [
        'label'   => esc_html__( 'Display Layout', 'elementor-widgets' ),
        'type'    => Controls_Manager::SELECT,
        'default' => 'grid',
        'options' => [ 'grid' => 'Grid', 'carousel' => 'Carousel' ],
    ] );

    // RESPONSIVE SELECT — generates CSS per breakpoint
    $this->add_responsive_control( 'columns', [
        'label'          => esc_html__( 'Columns', 'elementor-widgets' ),
        'type'           => Controls_Manager::SELECT,
        'default'        => '3',
        'tablet_default' => '2',
        'mobile_default' => '1',
        'options'        => [ '1' => '1', '2' => '2', '3' => '3', '4' => '4' ],
        'condition'      => [ 'layout' => 'grid' ],          // hide when not grid
        'selectors'      => [
            '{{WRAPPER}} .ew-grid' => 'grid-template-columns: repeat({{VALUE}}, 1fr);',
        ],
    ] );

    $this->end_controls_section();
}

Common control types: TEXT, TEXTAREA, SELECT, NUMBER, SWITCHER, MEDIA, REPEATER.

Repeater

$repeater = new \Elementor\Repeater();

$repeater->add_control( 'client_name', [
    'label'   => esc_html__( 'Client Name', 'elementor-widgets' ),
    'type'    => Controls_Manager::TEXT,
    'default' => 'Jane Doe',
    'dynamic' => [ 'active' => true ],
] );

$this->add_control( 'testimonials', [
    'type'        => Controls_Manager::REPEATER,
    'fields'      => $repeater->get_controls(),
    'title_field' => '{{{ client_name }}}',
] );

Conditional controls

$this->add_control( 'autoplay_speed', [
    'type'      => Controls_Manager::NUMBER,
    'condition' => [ 'carousel_autoplay' => 'yes' ],  // key => expected value
] );

6. Controls — Style Tab

private function register_style_controls(): void {

    $this->start_controls_section( 'section_card_style', [
        'label' => esc_html__( 'Card', 'elementor-widgets' ),
        'tab'   => Controls_Manager::TAB_STYLE,
    ] );

    // COLOR with inline CSS selector
    $this->add_control( 'text_color', [
        'type'      => Controls_Manager::COLOR,
        'default'   => '#4a5568',
        'selectors' => [ '{{WRAPPER}} .ew-text' => 'color: {{VALUE}};' ],
    ] );

    // DIMENSIONS (padding / border-radius)
    $this->add_responsive_control( 'card_padding', [
        'type'       => Controls_Manager::DIMENSIONS,
        'size_units' => [ 'px', 'em', 'rem' ],
        'selectors'  => [
            '{{WRAPPER}} .ew-card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
        ],
    ] );

    // SLIDER
    $this->add_responsive_control( 'avatar_size', [
        'type'      => Controls_Manager::SLIDER,
        'range'     => [ 'px' => [ 'min' => 40, 'max' => 120 ] ],
        'default'   => [ 'size' => 64, 'unit' => 'px' ],
        'selectors' => [ '{{WRAPPER}} .ew-avatar' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};' ],
    ] );

    // GROUP CONTROLS
    $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [
        'name' => 'text_typography', 'selector' => '{{WRAPPER}} .ew-text',
    ] );
    $this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [
        'name' => 'card_shadow', 'selector' => '{{WRAPPER}} .ew-card',
    ] );
    $this->add_group_control( \Elementor\Group_Control_Border::get_type(), [
        'name' => 'card_border', 'selector' => '{{WRAPPER}} .ew-card',
    ] );
    $this->add_group_control( \Elementor\Group_Control_Background::get_type(), [
        'name' => 'card_bg', 'types' => [ 'classic', 'gradient' ], 'selector' => '{{WRAPPER}} .ew-card',
    ] );

    $this->end_controls_section();
}

7. Rendering

protected function render(): void {
    $settings = $this->get_settings_for_display();

    if ( empty( $settings['testimonials'] ) ) { return; }

    foreach ( $settings['testimonials'] as $item ) {
        $rating = (int) ( $item['rating'] ?? 5 );
        ?>
        <div class="ew-card">
            <div class="ew-stars"
                 aria-label="<?php printf( esc_attr__( '%d out of 5 stars', 'elementor-widgets' ), $rating ); ?>">
                <?php echo esc_html( str_repeat( '★', $rating ) . str_repeat( '☆', 5 - $rating ) ); ?>
            </div>

            <blockquote class="ew-text">
                <?php echo wp_kses_post( $item['testimonial_text'] ?? '' ); ?>
            </blockquote>

            <span class="ew-client-name"><?php echo esc_html( $item['client_name'] ?? '' ); ?></span>
        </div>
        <?php
    }
}

Escaping quick-reference:

Data Function
Plain text esc_html()
Attribute value esc_attr()
URL esc_url()
Trusted HTML wp_kses_post()
JSON → data-* wp_json_encode() + esc_attr()

8. JavaScript Integration

Pass PHP config via data-*; read it in JS. Avoids inline `