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:
Go to WordPress Dashboard → Appearance → Theme File Editor
Open the
functions.phpfilePaste the custom widget code at the bottom of the file
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:
Open any page with Elementor
Search for the widget name: Custom Filterable Gallery
Drag and drop it into your page
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.
