How to Create a Custom Filterable Gallery Widget in Elementor (Without Plugin)

Introduction

If you want to build a custom filterable gallery in Elementor (with tabs, slider, grid view, and hover effects) without using any heavy plugins, this tutorial will help you step-by-step.

In this guide, you will learn:

  • Where to paste the custom Elementor widget code

  • How the filterable gallery works

  • How to use it inside Elementor

  • SEO and performance benefits

This method is perfect for WordPress developers, designers, and agencies who want advanced Elementor functionality.

What is a Custom Filterable Gallery in Elementor?

A filterable gallery allows users to:

  • Filter projects by categories (eCommerce, Shopify, Real Estate, etc.)

  • Switch between slider and grid layouts

  • View images with hover effects and links

  • Navigate slides using arrows and dots

Unlike normal Elementor galleries, this custom widget gives you full control and better performance.

Where to Paste the Custom Elementor Widget Code?

Add Code in functions.php (Recommended)

You should paste the widget code inside your WordPress theme’s functions.php file.

Steps:

  1. Go to WordPress Dashboard → Appearance → Theme File Editor

  2. Open the functions.php file

  3. Paste the custom widget code at the bottom of the file

  4. Click Update File

✅ Best for small projects or testing.

            // This is Custom Filterable Gallery for Elementor
add_action('elementor/widgets/register', function($widgets_manager) {
    
    class Custom_Filterable_Gallery_Widget extends \Elementor\Widget_Base {
        
        public function get_name() {
            return 'custom_filterable_gallery';
        }
        
        public function get_title() {
            return 'Custom Filterable Gallery';
        }
        
        public function get_icon() {
            return 'eicon-gallery-grid';
        }
        
        public function get_categories() {
            return ['basic'];
        }
        
        protected function register_controls() {
            
            // Content Tab
            $this->start_controls_section(
                'content_section',
                [
                    'label' => 'Gallery Content',
                    'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
                ]
            );

            // View Mode
            $this->add_control(
                'view_mode',
                [
                    'label' => 'View Mode',
                    'type' => \Elementor\Controls_Manager::SELECT,
                    'default' => 'slider',
                    'options' => [
                        'slider' => 'Slider',
                        'grid' => 'Grid',
                    ],
                ]
            );

            // Default Tab
            $this->add_control(
                'default_tab',
                [
                    'label' => 'Default Active Tab',
                    'type' => \Elementor\Controls_Manager::SELECT,
                    'default' => 'all',
                    'options' => [
                        'all' => 'All Projects',
                        'recent' => 'Recent',
                        'ecommerce' => 'eCommerce',
                        'real-estate' => 'Real Estate',
                        'restaurant' => 'Restaurant',
                        'shopify' => 'Shopify',
                        'hvac' => 'HVAC',
                        'medical' => 'Medical Equipment',
                        'speakers' => 'Speakers/Mentors',
                    ],
                ]
            );

            // Tabs Repeater
            $repeater_tabs = new \Elementor\Repeater();

            $repeater_tabs->add_control(
                'tab_name',
                [
                    'label' => 'Tab Name',
                    'type' => \Elementor\Controls_Manager::TEXT,
                    'default' => 'New Tab',
                ]
            );

            $repeater_tabs->add_control(
                'tab_slug',
                [
                    'label' => 'Tab Slug',
                    'type' => \Elementor\Controls_Manager::TEXT,
                    'default' => 'new-tab',
                ]
            );

            $this->add_control(
                'tabs_list',
                [
                    'label' => 'Tabs',
                    'type' => \Elementor\Controls_Manager::REPEATER,
                    'fields' => $repeater_tabs->get_controls(),
                    'default' => [
                        [
                            'tab_name' => 'All Projects',
                            'tab_slug' => 'all',
                        ],
                        [
                            'tab_name' => 'Recent',
                            'tab_slug' => 'recent',
                        ],
                        [
                            'tab_name' => 'eCommerce', 
                            'tab_slug' => 'ecommerce',
                        ],
                        [
                            'tab_name' => 'Real Estate',
                            'tab_slug' => 'real-estate',
                        ],
                        [
                            'tab_name' => 'Restaurant',
                            'tab_slug' => 'restaurant',
                        ],
                        [
                            'tab_name' => 'Shopify',
                            'tab_slug' => 'shopify',
                        ],
                        [
                            'tab_name' => 'HVAC',
                            'tab_slug' => 'hvac',
                        ],
                        [
                            'tab_name' => 'Medical Equipment',
                            'tab_slug' => 'medical',
                        ],
                        [
                            'tab_name' => 'Speakers/Mentors',
                            'tab_slug' => 'speakers',
                        ],
                    ],
                    'title_field' => '{{{ tab_name }}}',
                ]
            );

            // Slides Repeater
            $repeater_slides = new \Elementor\Repeater();

            $repeater_slides->add_control(
                'slide_image',
                [
                    'label' => 'Choose Image',
                    'type' => \Elementor\Controls_Manager::MEDIA,
                    'default' => [
                        'url' => \Elementor\Utils::get_placeholder_image_src(),
                    ],
                ]
            );

            $repeater_slides->add_control(
                'slide_title',
                [
                    'label' => 'Title',
                    'type' => \Elementor\Controls_Manager::TEXT,
                    'default' => 'Project Title',
                ]
            );

            $repeater_slides->add_control(
                'slide_category',
                [
                    'label' => 'Category',
                    'type' => \Elementor\Controls_Manager::SELECT,
                    'default' => 'recent',
                    'options' => [
                        'recent' => 'Recent',
                        'ecommerce' => 'eCommerce',
                        'real-estate' => 'Real Estate',
                        'restaurant' => 'Restaurant',
                        'shopify' => 'Shopify Project',
                        'hvac' => 'HVAC Project',
                        'medical' => 'Medical Equipment',
                        'speakers' => 'Speakers/Mentors',
                    ],
                ]
            );

            $repeater_slides->add_control(
                'slide_link',
                [
                    'label' => 'Link',
                    'type' => \Elementor\Controls_Manager::URL,
                    'placeholder' => 'https://your-project-link.com',
                    'show_external' => true,
                    'default' => [
                        'url' => '#',
                        'is_external' => false,
                        'nofollow' => false,
                    ],
                ]
            );

            $this->add_control(
                'slides_list',
                [
                    'label' => 'Slides',
                    'type' => \Elementor\Controls_Manager::REPEATER,
                    'fields' => $repeater_slides->get_controls(),
                    'default' => [
                        [
                            'slide_title' => 'Recent Project 1',
                            'slide_category' => 'recent',
                        ],
                        [
                            'slide_title' => 'Online Store', 
                            'slide_category' => 'ecommerce',
                        ],
                        [
                            'slide_title' => 'Property Website',
                            'slide_category' => 'real-estate',
                        ],
                        [
                            'slide_title' => 'Restaurant Booking',
                            'slide_category' => 'restaurant',
                        ],
                        [
                            'slide_title' => 'Shopify Store',
                            'slide_category' => 'shopify',
                        ],
                        [
                            'slide_title' => 'HVAC Service',
                            'slide_category' => 'hvac',
                        ],
                        [
                            'slide_title' => 'Medical Equipment',
                            'slide_category' => 'medical',
                        ],
                        [
                            'slide_title' => 'Speaker Profile',
                            'slide_category' => 'speakers',
                        ],
                    ],
                    'title_field' => '{{{ slide_title }}}',
                ]
            );

            $this->end_controls_section();

            // Slider Settings Section
            $this->start_controls_section(
                'slider_settings',
                [
                    'label' => 'Slider Settings',
                    'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
                ]
            );

            $this->add_control(
                'autoplay',
                [
                    'label' => 'Auto Slide',
                    'type' => \Elementor\Controls_Manager::SWITCHER,
                    'label_on' => 'On',
                    'label_off' => 'Off',
                    'return_value' => 'yes',
                    'default' => 'no',
                    'condition' => [
                        'view_mode' => 'slider',
                    ],
                ]
            );

            $this->add_control(
                'autoplay_speed',
                [
                    'label' => 'Auto Slide Speed (ms)',
                    'type' => \Elementor\Controls_Manager::NUMBER,
                    'min' => 1000,
                    'max' => 10000,
                    'step' => 500,
                    'default' => 3000,
                    'condition' => [
                        'autoplay' => 'yes',
                        'view_mode' => 'slider',
                    ],
                ]
            );

            $this->add_control(
                'slides_to_show',
                [
                    'label' => 'Slides to Show',
                    'type' => \Elementor\Controls_Manager::NUMBER,
                    'min' => 1,
                    'max' => 6,
                    'step' => 1,
                    'default' => 3,
                    'condition' => [
                        'view_mode' => 'slider',
                    ],
                ]
            );

            $this->add_control(
                'show_navigation',
                [
                    'label' => 'Show Navigation Arrows',
                    'type' => \Elementor\Controls_Manager::SWITCHER,
                    'label_on' => 'Show',
                    'label_off' => 'Hide',
                    'return_value' => 'yes',
                    'default' => 'yes', // Changed to yes by default
                    'condition' => [
                        'view_mode' => 'slider',
                    ],
                ]
            );

            // NEW: Dots visibility control
            $this->add_control(
                'show_dots',
                [
                    'label' => 'Show Navigation Dots',
                    'type' => \Elementor\Controls_Manager::SWITCHER,
                    'label_on' => 'Show',
                    'label_off' => 'Hide',
                    'return_value' => 'yes',
                    'default' => 'yes',
                    'condition' => [
                        'view_mode' => 'slider',
                    ],
                ]
            );

            $this->end_controls_section();

            // Grid Settings Section
            $this->start_controls_section(
                'grid_settings',
                [
                    'label' => 'Grid Settings',
                    'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
                    'condition' => [
                        'view_mode' => 'grid',
                    ],
                ]
            );

            $this->add_control(
                'columns',
                [
                    'label' => 'Columns',
                    'type' => \Elementor\Controls_Manager::SELECT,
                    'default' => '3',
                    'options' => [
                        '1' => '1 Column',
                        '2' => '2 Columns',
                        '3' => '3 Columns',
                        '4' => '4 Columns',
                        '5' => '5 Columns',
                        '6' => '6 Columns',
                    ],
                ]
            );

            $this->end_controls_section();

            // Style Tab
            $this->start_controls_section(
                'style_section',
                [
                    'label' => 'Style',
                    'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                ]
            );

            // Container Styles
            $this->add_control(
                'container_style_heading',
                [
                    'label' => 'Container Style',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                ]
            );

            $this->add_responsive_control(
                'container_padding',
                [
                    'label' => 'Container Padding',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-filter-gallery' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            $this->add_responsive_control(
                'container_margin',
                [
                    'label' => 'Container Margin',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-filter-gallery' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            // Tab Styles
            $this->add_control(
                'tab_style_heading',
                [
                    'label' => 'Tabs Style',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                ]
            );

            $this->add_responsive_control(
                'tab_padding',
                [
                    'label' => 'Tab Padding',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            $this->add_responsive_control(
                'tab_margin',
                [
                    'label' => 'Tab Margin',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            $this->add_control(
                'tab_bg_color',
                [
                    'label' => 'Tab Background Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#f5f5f5',
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn' => 'background-color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'tab_active_bg_color',
                [
                    'label' => 'Active Tab Background',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#4a6ee0',
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn.active' => 'background-color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'tab_text_color',
                [
                    'label' => 'Tab Text Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#333333',
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn' => 'color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'tab_active_text_color',
                [
                    'label' => 'Active Tab Text Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#ffffff',
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn.active' => 'color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'tab_border_color',
                [
                    'label' => 'Tab Border Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#dddddd',
                    'selectors' => [
                        '{{WRAPPER}} .custom-tab-btn' => 'border: 1px solid {{VALUE}}',
                    ],
                ]
            );

            // Slide Styles
            $this->add_control(
                'slide_style_heading',
                [
                    'label' => 'Slide Style',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                ]
            );

            $this->add_responsive_control(
                'slide_padding',
                [
                    'label' => 'Slide Padding',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            $this->add_responsive_control(
                'slide_margin',
                [
                    'label' => 'Slide Margin',
                    'type' => \Elementor\Controls_Manager::DIMENSIONS,
                    'size_units' => ['px', '%', 'em'],
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                    ],
                ]
            );

            $this->add_control(
                'slide_bg_color',
                [
                    'label' => 'Slide Background',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#ffffff',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide' => 'background-color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'slide_border_color',
                [
                    'label' => 'Slide Border Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#e0e0e0',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide' => 'border: 1px solid {{VALUE}}',
                    ],
                ]
            );
            
            $this->add_control(
                'slide_border_color_hover',
                [
                    'label' => 'Slide Border Color - Hover',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#4a6ee0',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide:hover' => 'border-color: {{VALUE}}',
                    ],
                ]
            );

            $this->add_control(
                'slide_hover_transition',
                [
                    'label' => 'Hover Transition Duration (ms)',
                    'type' => \Elementor\Controls_Manager::NUMBER,
                    'min' => 0,
                    'max' => 1000,
                    'step' => 50,
                    'default' => 300,
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide' => 'transition: border-color {{VALUE}}ms ease;',
                    ],
                ]
            );

            // Image Hover Effects Heading
            $this->add_control(
                'image_hover_effects_heading',
                [
                    'label' => 'Image Hover Effects',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                ]
            );

            // Image Hover Opacity
            $this->add_control(
                'image_hover_opacity',
                [
                    'label' => 'Image Hover Opacity',
                    'type' => \Elementor\Controls_Manager::SLIDER,
                    'range' => [
                        'px' => [
                            'max' => 1,
                            'min' => 0.10,
                            'step' => 0.01,
                        ],
                    ],
                    'default' => [
                        'size' => 0.8,
                    ],
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide:hover .custom-slide-image' => 'opacity: {{SIZE}};',
                    ],
                ]
            );

            // Hover Arrow Icon (SINGLE - no duplicates)
            $this->add_control(
                'show_hover_arrow',
                [
                    'label' => 'Show Hover Arrow Icon',
                    'type' => \Elementor\Controls_Manager::SWITCHER,
                    'label_on' => 'Show',
                    'label_off' => 'Hide',
                    'return_value' => 'yes',
                    'default' => 'yes',
                ]
            );

            $this->add_control(
                'hover_arrow_color',
                [
                    'label' => 'Hover Arrow Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#ffffff',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide-hover-arrow' => 'color: {{VALUE}}',
                    ],
                    'condition' => [
                        'show_hover_arrow' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'hover_arrow_bg',
                [
                    'label' => 'Hover Arrow Background',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => 'rgba(74, 110, 224, 0.9)',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide-hover-arrow' => 'background-color: {{VALUE}}',
                    ],
                    'condition' => [
                        'show_hover_arrow' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'title_color',
                [
                    'label' => 'Title Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#333333',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slide-title' => 'color: {{VALUE}}',
                    ],
                ]
            );

            // Navigation Styles
            $this->add_control(
                'nav_style_heading',
                [
                    'label' => 'Navigation Style',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_navigation' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'nav_bg_color',
                [
                    'label' => 'Arrow Background',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => 'rgba(0, 0, 0, 0.7)',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slider-nav' => 'background-color: {{VALUE}}',
                    ],
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_navigation' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'nav_hover_bg_color',
                [
                    'label' => 'Arrow Hover Background',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => 'rgba(0, 0, 0, 0.9)',
                    'selectors' => [
                        '{{WRAPPER}} .custom-slider-nav:hover' => 'background-color: {{VALUE}}',
                    ],
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_navigation' => 'yes',
                    ],
                ]
            );

            // Dots Styles
            $this->add_control(
                'dots_style_heading',
                [
                    'label' => 'Dots Style',
                    'type' => \Elementor\Controls_Manager::HEADING,
                    'separator' => 'before',
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_dots' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'dots_color',
                [
                    'label' => 'Dots Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#dddddd',
                    'selectors' => [
                        '{{WRAPPER}} .custom-dot' => 'background-color: {{VALUE}}',
                    ],
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_dots' => 'yes',
                    ],
                ]
            );

            $this->add_control(
                'dots_active_color',
                [
                    'label' => 'Active Dot Color',
                    'type' => \Elementor\Controls_Manager::COLOR,
                    'default' => '#4a6ee0',
                    'selectors' => [
                        '{{WRAPPER}} .custom-dot.active' => 'background-color: {{VALUE}}',
                    ],
                    'condition' => [
                        'view_mode' => 'slider',
                        'show_dots' => 'yes',
                    ],
                ]
            );

            $this->end_controls_section();
        }
        
        protected function render() {
            $settings = $this->get_settings_for_display();
            $widget_id = $this->get_id();
            $default_tab = $settings['default_tab'];
            
            // Find the active tab index based on default_tab
            $active_tab_index = 0;
            foreach ($settings['tabs_list'] as $index => $tab) {
                if ($tab['tab_slug'] === $default_tab) {
                    $active_tab_index = $index;
                    break;
                }
            }
            ?>
            
            <div class="custom-filter-gallery custom-view-<?php echo esc_attr($settings['view_mode']); ?>" 
                 id="gallery-<?php echo $widget_id; ?>" 
                 data-autoplay="<?php echo $settings['autoplay']; ?>"
                 data-autoplay-speed="<?php echo $settings['autoplay_speed']; ?>"
                 data-view-mode="<?php echo esc_attr($settings['view_mode']); ?>"
                 data-show-navigation="<?php echo $settings['show_navigation']; ?>"
                 data-show-dots="<?php echo $settings['show_dots']; ?>"
                 data-default-tab="<?php echo esc_attr($default_tab); ?>"
                 data-columns="<?php echo esc_attr($settings['columns']); ?>">
                
                <!-- Tabs -->
                <div class="custom-filter-tabs">
                    <?php foreach ($settings['tabs_list'] as $index => $tab): ?>
                        <button class="custom-tab-btn <?php echo ($index === $active_tab_index) ? 'active' : ''; ?>" 
                                data-filter="<?php echo esc_attr($tab['tab_slug']); ?>">
                            <?php echo esc_html($tab['tab_name']); ?>
                        </button>
                    <?php endforeach; ?>
                </div>

                <!-- Slider/Grid Container -->
                <div class="custom-gallery-container <?php echo ($settings['view_mode'] === 'grid') ? 'custom-grid-layout' : 'custom-slider-layout'; ?>">
                    <?php if ($settings['view_mode'] === 'slider'): ?>
                        <!-- Slider Layout -->
                        <div class="custom-slider-container">
                            <div class="custom-slider-track">
                                <?php foreach ($settings['slides_list'] as $slide): 
                                    $target = $slide['slide_link']['is_external'] ? ' target="_blank"' : '';
                                    $nofollow = $slide['slide_link']['nofollow'] ? ' rel="nofollow"' : '';
                                    $display_style = ($default_tab === 'all' || $slide['slide_category'] === $default_tab) ? '' : 'display: none;';
                                ?>
                                    <div class="custom-slide" 
                                         data-category="<?php echo esc_attr($slide['slide_category']); ?>"
                                         style="<?php echo $display_style; ?>">
                                        <a href="<?php echo esc_url($slide['slide_link']['url']); ?>" 
                                           class="custom-slide-link"
                                           <?php echo $target . $nofollow; ?>>
                                            <img src="<?php echo esc_url($slide['slide_image']['url']); ?>" 
                                                 alt="<?php echo esc_attr($slide['slide_title']); ?>" 
                                                 class="custom-slide-image">
                                            
                                            <?php if ($settings['show_hover_arrow'] === 'yes'): ?>
                                            <div class="custom-slide-hover-arrow">
                                                →
                                            </div>
                                            <?php endif; ?>
                                            
                                            <div class="custom-slide-content">
                                                <h3 class="custom-slide-title"><?php echo esc_html($slide['slide_title']); ?></h3>
                                            </div>
                                        </a>
                                    </div>
                                <?php endforeach; ?>
                            </div>

                            <!-- Navigation Arrows - Conditionally Displayed -->
                            <?php if ($settings['show_navigation'] === 'yes'): ?>
                                <button class="custom-slider-nav custom-prev-btn">❮</button>
                                <button class="custom-slider-nav custom-next-btn">❯</button>
                            <?php endif; ?>
                        </div>

                        <!-- Dots - Conditionally Displayed -->
                        <?php if ($settings['show_dots'] === 'yes'): ?>
                            <div class="custom-slide-dots"></div>
                        <?php endif; ?>

                    <?php else: ?>
                        <!-- Grid Layout -->
                        <div class="custom-grid-container">
                            <div class="custom-grid-track">
                                <?php foreach ($settings['slides_list'] as $slide): 
                                    $target = $slide['slide_link']['is_external'] ? ' target="_blank"' : '';
                                    $nofollow = $slide['slide_link']['nofollow'] ? ' rel="nofollow"' : '';
                                    $display_style = ($default_tab === 'all' || $slide['slide_category'] === $default_tab) ? '' : 'display: none;';
                                ?>
                                    <div class="custom-slide custom-grid-item" 
                                         data-category="<?php echo esc_attr($slide['slide_category']); ?>"
                                         style="<?php echo $display_style; ?>">
                                        <a href="<?php echo esc_url($slide['slide_link']['url']); ?>" 
                                           class="custom-slide-link"
                                           <?php echo $target . $nofollow; ?>>
                                            <img src="<?php echo esc_url($slide['slide_image']['url']); ?>" 
                                                 alt="<?php echo esc_attr($slide['slide_title']); ?>" 
                                                 class="custom-slide-image">
                                            
                                            <?php if ($settings['show_hover_arrow'] === 'yes'): ?>
                                            <div class="custom-slide-hover-arrow">
                                                →
                                            </div>
                                            <?php endif; ?>
                                            
                                            <div class="custom-slide-content">
                                                <h3 class="custom-slide-title"><?php echo esc_html($slide['slide_title']); ?></h3>
                                            </div>
                                        </a>
                                    </div>
                                <?php endforeach; ?>
                            </div>
                        </div>
                    <?php endif; ?>
                </div>
            </div>

            <style>
            .custom-filter-gallery {
                font-family: Arial, sans-serif;
                max-width: 1200px;
                margin: 0 auto;
            }

            .custom-filter-tabs {
                display: flex;
                justify-content: center;
                flex-wrap: wrap;
                gap: 10px;
                margin-bottom: 30px;
            }

            .custom-tab-btn {
                padding: 10px 20px;
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.3s ease;
                font-size: 14px;
                border: 1px solid #dddddd;
                background-color: #f5f5f5;
                color: #333333;
            }

            .custom-tab-btn:hover {
                opacity: 0.9;
            }

            .custom-tab-btn.active {
                background-color: #4a6ee0;
                color: #ffffff;
                opacity: 1;
            }

            /* Slider Layout Styles */
            .custom-slider-layout .custom-slider-container {
                position: relative;
                overflow: hidden;
                margin: 0 auto;
            }

            .custom-slider-layout .custom-slider-track {
                display: flex;
                transition: transform 0.5s ease;
                gap: 20px;
            }

            .custom-slider-layout .custom-slide {
                flex: 0 0 calc(33.333% - 14px);
                border-radius: 8px;
                overflow: hidden;
                display: flex;
                flex-direction: column;
                background-color: #ffffff;
                border: 1px solid #e0e0e0;
            }

            /* Arrow positioning INSIDE the slider section */
            .custom-slider-nav {
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
                color: white;
                border: none;
                width: 40px;
                height: 40px;
                border-radius: 50%;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 10;
                transition: background 0.3s;
                background-color: rgba(0, 0, 0, 0.7);
            }

            .custom-slider-nav:hover {
                background-color: rgba(0, 0, 0, 0.9);
            }

            .custom-prev-btn {
                left: 10px;
            }

            .custom-next-btn {
                right: 10px;
            }

            /* Grid Layout Styles */
            .custom-grid-layout .custom-grid-container {
                width: 100%;
            }

            .custom-grid-layout .custom-grid-track {
                display: grid;
                gap: 20px;
                grid-template-columns: repeat(3, 1fr);
            }

            .custom-grid-layout .custom-grid-item {
                border-radius: 8px;
                overflow: hidden;
                display: flex;
                flex-direction: column;
                background-color: #ffffff;
                border: 1px solid #e0e0e0;
            }

            /* Common Styles for Both Layouts */
            .custom-slide-link {
                position: relative;
                display: block;
                text-decoration: none;
                color: inherit;
                display: flex;
                flex-direction: column;
                height: 100%;
            }

            .custom-slide-image {
                width: 100%;
                height: 200px;
                object-fit: cover;
                display: block;
            }

            .custom-slide-content {
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                padding: 15px;
            }

            .custom-slide-title {
                margin: 0;
                font-size: 16px;
                font-weight: bold;
                color: #333333;
            }

            .custom-slide-dots {
                display: flex;
                justify-content: center;
                margin-top: 20px;
                gap: 8px;
            }

            .custom-dot {
                width: 10px;
                height: 10px;
                border-radius: 50%;
                background-color: #ddd;
                cursor: pointer;
                transition: background-color 0.3s;
            }

            .custom-dot.active {
                background-color: #4a6ee0;
            }

            /* Grid Columns */
            .custom-grid-layout.columns-1 .custom-grid-track {
                grid-template-columns: repeat(1, 1fr);
            }

            .custom-grid-layout.columns-2 .custom-grid-track {
                grid-template-columns: repeat(2, 1fr);
            }

            .custom-grid-layout.columns-3 .custom-grid-track {
                grid-template-columns: repeat(3, 1fr);
            }

            .custom-grid-layout.columns-4 .custom-grid-track {
                grid-template-columns: repeat(4, 1fr);
            }

            .custom-grid-layout.columns-5 .custom-grid-track {
                grid-template-columns: repeat(5, 1fr);
            }

            .custom-grid-layout.columns-6 .custom-grid-track {
                grid-template-columns: repeat(6, 1fr);
            }

            /* Hover Arrow Icon */
            .custom-slide-hover-arrow {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%) scale(0);
                width: 50px;
                height: 50px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                background: rgba(74, 110, 224, 0.9);
                color: white;
                font-size: 20px;
                opacity: 0;
                transition: all 0.3s ease;
                z-index: 2;
            }

            .custom-slide:hover .custom-slide-hover-arrow {
                opacity: 1;
                transform: translate(-50%, -50%) scale(1);
            }

            /* Mobile Responsive */
            @media (max-width: 992px) {
                .custom-slider-layout .custom-slide {
                    flex: 0 0 calc(50% - 10px);
                }
                
                .custom-grid-layout .custom-grid-track {
                    grid-template-columns: repeat(2, 1fr);
                }
                
                .custom-grid-layout.columns-4 .custom-grid-track,
                .custom-grid-layout.columns-5 .custom-grid-track,
                .custom-grid-layout.columns-6 .custom-grid-track {
                    grid-template-columns: repeat(3, 1fr);
                }
                
                .custom-filter-tabs {
                    gap: 8px;
                }
                
                .custom-tab-btn {
                    padding: 8px 16px;
                    font-size: 13px;
                }
            }

            @media (max-width: 768px) {
                .custom-filter-tabs {
                    flex-direction: column;
                    align-items: center;
                }
                
                .custom-tab-btn {
                    width: 100%;
                    margin-bottom: 5px;
                }
                
                .custom-slider-layout .custom-slide {
                    flex: 0 0 100%;
                }
                
                .custom-grid-layout .custom-grid-track {
                    grid-template-columns: repeat(1, 1fr);
                }
                
                .custom-grid-layout.columns-2 .custom-grid-track,
                .custom-grid-layout.columns-3 .custom-grid-track,
                .custom-grid-layout.columns-4 .custom-grid-track,
                .custom-grid-layout.columns-5 .custom-grid-track,
                .custom-grid-layout.columns-6 .custom-grid-track {
                    grid-template-columns: repeat(2, 1fr);
                }
                
                .custom-slider-nav {
                    width: 35px;
                    height: 35px;
                }
            }

            @media (max-width: 480px) {
                .custom-slide-image {
                    height: 180px;
                }
                
                .custom-filter-tabs {
                    gap: 5px;
                }
                
                .custom-tab-btn {
                    padding: 6px 12px;
                    font-size: 12px;
                }
                
                .custom-grid-layout .custom-grid-track,
                .custom-grid-layout.columns-2 .custom-grid-track,
                .custom-grid-layout.columns-3 .custom-grid-track,
                .custom-grid-layout.columns-4 .custom-grid-track,
                .custom-grid-layout.columns-5 .custom-grid-track,
                .custom-grid-layout.columns-6 .custom-grid-track {
                    grid-template-columns: repeat(1, 1fr);
                }
            }
            </style>

            <script>
            jQuery(document).ready(function($) {
                function initGallery(galleryId) {
                    const gallery = $('#' + galleryId);
                    const tabs = gallery.find('.custom-tab-btn');
                    const slides = gallery.find('.custom-slide');
                    const viewMode = gallery.data('view-mode');
                    const showNavigation = gallery.data('show-navigation') === 'yes';
                    const showDots = gallery.data('show-dots') === 'yes';
                    const columns = gallery.data('columns');
                    const defaultTab = gallery.data('default-tab');
                    
                    // Apply grid columns class
                    if (viewMode === 'grid') {
                        gallery.find('.custom-gallery-container').addClass('columns-' + columns);
                    }
                    
                    let currentSlide = 0;
                    let slidesToShow = 3;
                    let filteredSlides = slides;
                    let autoplayInterval;
                    let isHovering = false;
                    
                    // Only initialize slider-specific functionality if in slider mode
                    if (viewMode === 'slider') {
                        const track = gallery.find('.custom-slider-track');
                        const prevBtn = gallery.find('.custom-prev-btn');
                        const nextBtn = gallery.find('.custom-next-btn');
                        const dotsContainer = gallery.find('.custom-slide-dots');
                        
                        const autoplay = gallery.data('autoplay') === 'yes';
                        const autoplaySpeed = parseInt(gallery.data('autoplay-speed')) || 3000;
                        
                        function calculateSlidesToShow() {
                            if (window.innerWidth < 768) return 1;
                            if (window.innerWidth < 992) return 2;
                            return 3;
                        }
                        
                        // Arrow navigation functionality
                        function goToPrevSlide() {
                            const totalSlides = filteredSlides.filter(':visible').length;
                            if (currentSlide > 0) {
                                currentSlide--;
                            } else {
                                currentSlide = Math.max(0, totalSlides - slidesToShow);
                            }
                            updateSlider();
                            updateDots();
                        }
                        
                        function goToNextSlide() {
                            const totalSlides = filteredSlides.filter(':visible').length;
                            if (currentSlide < totalSlides - slidesToShow) {
                                currentSlide++;
                            } else {
                                currentSlide = 0;
                            }
                            updateSlider();
                            updateDots();
                        }
                        
                        function updateSlider() {
                            const slideWidth = 100 / slidesToShow;
                            const translateX = -currentSlide * slideWidth;
                            track.css('transform', 'translateX(' + translateX + '%)');
                        }
                        
                        function createDots() {
                            dotsContainer.empty();
                            
                            if (window.innerWidth >= 768 || !showDots) return;
                            
                            const totalSlides = filteredSlides.filter(':visible').length;
                            const dotCount = Math.max(1, totalSlides - slidesToShow + 1);
                            
                            for (let i = 0; i < dotCount; i++) {
                                const dot = $('<div class="custom-dot"></div>');
                                if (i === currentSlide) dot.addClass('active');
                                
                                dot.on('click', function() {
                                    currentSlide = i;
                                    updateSlider();
                                    updateDots();
                                    resetAutoplay();
                                });
                                
                                dotsContainer.append(dot);
                            }
                        }
                        
                        function updateDots() {
                            const dots = dotsContainer.find('.custom-dot');
                            dots.removeClass('active');
                            if (dots.eq(currentSlide).length) {
                                dots.eq(currentSlide).addClass('active');
                            }
                        }
                        
                        function startAutoplay() {
                            if (!autoplay || isHovering) return;
                            stopAutoplay();
                            
                            autoplayInterval = setInterval(function() {
                                goToNextSlide();
                            }, autoplaySpeed);
                        }
                        
                        function resetAutoplay() {
                            if (autoplay && !isHovering) {
                                stopAutoplay();
                                startAutoplay();
                            }
                        }
                        
                        function stopAutoplay() {
                            if (autoplayInterval) {
                                clearInterval(autoplayInterval);
                                autoplayInterval = null;
                            }
                        }
                        
                        // Arrow click events
                        if (showNavigation) {
                            prevBtn.on('click', function() {
                                goToPrevSlide();
                                resetAutoplay();
                            });
                            
                            nextBtn.on('click', function() {
                                goToNextSlide();
                                resetAutoplay();
                            });
                        }
                        
                        // Handle resize for slider
                        function handleResize() {
                            slidesToShow = calculateSlidesToShow();
                            updateSlider();
                            createDots();
                        }
                        
                        $(window).on('resize', handleResize);
                        
                        // Hover functionality
                        gallery.on('mouseenter', function() {
                            isHovering = true;
                            stopAutoplay();
                        });

                        gallery.on('mouseleave', function() {
                            isHovering = false;
                            if (autoplay) {
                                startAutoplay();
                            }
                        });
                        
                        // Initialize slider
                        slidesToShow = calculateSlidesToShow();
                        updateSlider();
                        createDots();
                        
                        // Start autoplay only if enabled
                        if (autoplay) {
                            startAutoplay();
                        }
                    }
                    
                    // Tab functionality (works for both slider and grid)
                    tabs.on('click', function() {
                        tabs.removeClass('active');
                        $(this).addClass('active');
                        
                        const filter = $(this).data('filter');
                        filterSlides(filter);
                    });
                    
                    function filterSlides(filter) {
                        if (filter === 'all') {
                            filteredSlides = slides;
                            slides.show();
                        } else {
                            filteredSlides = slides.filter('[data-category="' + filter + '"]');
                            slides.hide();
                            filteredSlides.show();
                        }
                        
                        if (viewMode === 'slider') {
                            currentSlide = 0;
                            updateSlider();
                            createDots();
                            resetAutoplay();
                        }
                    }
                    
                    // Initialize with default tab
                    filterSlides(defaultTab);
                }
                
                initGallery('gallery-<?php echo $widget_id; ?>');
            });
            </script>
            
            <?php
        }
    }
    
    $widgets_manager->register(new Custom_Filterable_Gallery_Widget());
});

// Enqueue jQuery
add_action('wp_enqueue_scripts', function() {
    wp_enqueue_script('jquery');
});
        

How to Use the Filterable Gallery in Elementor?

After adding the code:

  1. Open any page with Elementor

  2. Search for the widget name: Custom Filterable Gallery

  3. Drag and drop it into your page

  4. Configure settings like:

    • View Mode (Slider / Grid)

    • Tabs (Categories)

    • Images and project links

    • Autoplay, arrows, dots

    • Columns and layout styles

You can fully customize the gallery without writing extra code.

Features of This Custom Elementor Gallery

  • Tab-based filtering system

  • Slider and grid layout options

  • Responsive design for mobile

  • Hover arrow animation

  • Custom styling from Elementor panel

  • SEO-friendly structure

  • Lightweight and fast loading

Why Use a Custom Elementor Widget Instead of Plugins?

Using too many plugins can:

  • Slow down your website

  • Increase security risks

  • Limit customization

A custom Elementor widget:

  • Improves performance

  • Gives full design control

  • Reduces dependency on plugins

  • Makes your website more professional

SEO Benefits of a Custom Filterable Gallery

This gallery helps SEO because:

  • Clean HTML structure improves crawlability

  • Images can have optimized alt tags

  • Internal links increase page authority

  • Better UX improves engagement metrics

This can help your WordPress website rank higher on Google.

Conclusion

By creating a custom filterable gallery widget in Elementor, you can build advanced portfolios, project showcases, and service galleries without heavy plugins.

If you are a WordPress developer or designer, this technique will give you a professional edge and improve website performance.

Leave a Reply

Your email address will not be published. Required fields are marked *