namespace Elementor\Modules\NestedTabs\Widgets;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Documents\Tabs\Global_Colors;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Group_Control_Background;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Group_Control_Text_Stroke;
use Elementor\Group_Control_Typography;
use Elementor\Icons_Manager;
use Elementor\Modules\NestedElements\Base\Widget_Nested_Base;
use Elementor\Modules\NestedElements\Controls\Control_Nested_Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
class NestedTabs extends Widget_Nested_Base {
private $tab_item_settings = [];
private $optimized_markup = null;
private $widget_container_selector = '';
public function get_name() {
public function get_title() {
return esc_html__( 'Tabs', 'elementor' );
public function get_icon() {
public function get_keywords() {
return [ 'nested', 'tabs', 'accordion', 'toggle' ];
public function get_style_depends(): array {
return [ 'widget-nested-tabs' ];
public function has_widget_inner_wrapper(): bool {
return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
public function show_in_panel(): bool {
return Plugin::$instance->experiments->is_feature_active( 'nested-elements', true );
protected function tab_content_container( int $index ) {
/* translators: %d: Tab index. */
__( 'Tab #%d', 'elementor' ),
'content_width' => 'full',
protected function get_default_children_elements() {
$this->tab_content_container( 1 ),
$this->tab_content_container( 2 ),
$this->tab_content_container( 3 ),
protected function get_default_repeater_title_setting_key() {
protected function get_default_children_title() {
/* translators: %d: Tab index. */
return esc_html__( 'Tab #%d', 'elementor' );
protected function get_default_children_placeholder_selector() {
return '.e-n-tabs-content';
protected function get_html_wrapper_class() {
return 'elementor-widget-n-tabs';
protected function register_controls() {
if ( null === $this->optimized_markup ) {
$this->optimized_markup = Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ) && ! $this->has_widget_inner_wrapper();
$this->widget_container_selector = $this->optimized_markup ? '' : ' > .elementor-widget-container';
$start = is_rtl() ? 'right' : 'left';
$end = is_rtl() ? 'left' : 'right';
$start_logical = is_rtl() ? 'end' : 'start';
$end_logical = is_rtl() ? 'start' : 'end';
$heading_selector_non_touch_device = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs[data-touch-mode='false'] > .e-n-tabs-heading";
$heading_selector_touch_device = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs[data-touch-mode='true'] > .e-n-tabs-heading";
$heading_selector = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs > .e-n-tabs-heading";
$content_selector = ":where( {{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs > .e-n-tabs-content ) > .e-con";
$this->start_controls_section( 'section_tabs', [
'label' => esc_html__( 'Tabs', 'elementor' ),
$repeater = new Repeater();
$repeater->add_control( 'tab_title', [
'label' => esc_html__( 'Title', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Tab Title', 'elementor' ),
'placeholder' => esc_html__( 'Tab Title', 'elementor' ),
'label' => esc_html__( 'Icon', 'elementor' ),
'type' => Controls_Manager::ICONS,
'fa4compatibility' => 'icon',
'label' => esc_html__( 'Active Icon', 'elementor' ),
'type' => Controls_Manager::ICONS,
'fa4compatibility' => 'icon',
'tab_icon[value]!' => '',
'label' => esc_html__( 'CSS ID', 'elementor' ),
'type' => Controls_Manager::TEXT,
'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ),
'style_transfer' => false,
'classes' => 'elementor-control-direction-ltr',
$this->add_control( 'tabs', [
'label' => esc_html__( 'Tabs Items', 'elementor' ),
'type' => Control_Nested_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
'tab_title' => esc_html__( 'Tab #1', 'elementor' ),
'tab_title' => esc_html__( 'Tab #2', 'elementor' ),
'tab_title' => esc_html__( 'Tab #3', 'elementor' ),
'title_field' => '{{{ tab_title }}}',
'button_text' => esc_html__( 'Add Tab', 'elementor' ),
$styling_block_start = '--n-tabs-direction: column; --n-tabs-heading-direction: row; --n-tabs-heading-width: initial; --n-tabs-title-flex-basis: content; --n-tabs-title-flex-shrink: 0;';
$styling_block_end = '--n-tabs-direction: column-reverse; --n-tabs-heading-direction: row; --n-tabs-heading-width: initial; --n-tabs-title-flex-basis: content; --n-tabs-title-flex-shrink: 0';
$styling_inline_end = '--n-tabs-direction: row-reverse; --n-tabs-heading-direction: column; --n-tabs-heading-width: 240px; --n-tabs-title-flex-basis: initial; --n-tabs-title-flex-shrink: initial;';
$styling_inline_start = '--n-tabs-direction: row; --n-tabs-heading-direction: column; --n-tabs-heading-width: 240px; --n-tabs-title-flex-basis: initial; --n-tabs-title-flex-shrink: initial;';
$this->add_responsive_control( 'tabs_direction', [
'label' => esc_html__( 'Direction', 'elementor' ),
'type' => Controls_Manager::CHOOSE,
'title' => esc_html__( 'Above', 'elementor' ),
'icon' => 'eicon-v-align-top',
'title' => esc_html__( 'Below', 'elementor' ),
'icon' => 'eicon-v-align-bottom',
'title' => esc_html__( 'After', 'elementor' ),
'icon' => 'eicon-h-align-' . $end,
'title' => esc_html__( 'Before', 'elementor' ),
'icon' => 'eicon-h-align-' . $start,
'selectors_dictionary' => [
'block-start' => $styling_block_start,
'block-end' => $styling_block_end,
'inline-end' => $styling_inline_end,
'inline-start' => $styling_inline_start,
// Styling duplication for BC reasons.
'top' => $styling_block_start,
'bottom' => $styling_block_end,
'end' => $styling_inline_end,
'start' => $styling_inline_start,
'{{WRAPPER}}' => '{{VALUE}}',
'control_type' => 'content',
$this->add_responsive_control( 'tabs_justify_horizontal', [
'label' => esc_html__( 'Justify', 'elementor' ),
'type' => Controls_Manager::CHOOSE,
'title' => esc_html__( 'Start', 'elementor' ),
'icon' => "eicon-align-$start_logical-h",
'title' => esc_html__( 'Center', 'elementor' ),
'icon' => 'eicon-align-center-h',
'title' => esc_html__( 'End', 'elementor' ),
'icon' => "eicon-align-$end_logical-h",
'title' => esc_html__( 'Stretch', 'elementor' ),
'icon' => 'eicon-align-stretch-h',
'selectors_dictionary' => [
'start' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;',
'center' => '--n-tabs-heading-justify-content: center; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;',
'end' => '--n-tabs-heading-justify-content: flex-end; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;',
'stretch' => '--n-tabs-heading-justify-content: initial; --n-tabs-title-width: 100%; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 1;',
'{{WRAPPER}}' => '{{VALUE}}',
'frontend_available' => true,
$this->add_responsive_control( 'tabs_justify_vertical', [
'label' => esc_html__( 'Justify', 'elementor' ),
'type' => Controls_Manager::CHOOSE,
'title' => esc_html__( 'Start', 'elementor' ),
'icon' => 'eicon-align-start-v',
'title' => esc_html__( 'Center', 'elementor' ),
'icon' => 'eicon-align-center-v',
'title' => esc_html__( 'End', 'elementor' ),
'icon' => 'eicon-align-end-v',
'title' => esc_html__( 'Stretch', 'elementor' ),
'icon' => 'eicon-align-stretch-v',
'selectors_dictionary' => [
'start' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content',
'center' => '--n-tabs-heading-justify-content: center; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content',
'end' => '--n-tabs-heading-justify-content: flex-end; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content',
'stretch' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: 100%; --n-tabs-title-align-items: center; --n-tabs-heading-wrap: nowrap; --n-tabs-title-flex-basis: auto',
'{{WRAPPER}}' => '{{VALUE}}',
$this->add_responsive_control( 'tabs_width', [
'label' => esc_html__( 'Width', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'{{WRAPPER}}' => '--n-tabs-heading-width: {{SIZE}}{{UNIT}}',
$this->add_responsive_control( 'title_alignment', [
'label' => esc_html__( 'Align Title', 'elementor' ),
'type' => Controls_Manager::CHOOSE,
'title' => esc_html__( 'Start', 'elementor' ),
'icon' => 'eicon-text-align-left',
'title' => esc_html__( 'Center', 'elementor' ),
'icon' => 'eicon-text-align-center',
'title' => esc_html__( 'End', 'elementor' ),
'icon' => 'eicon-text-align-right',
'selectors_dictionary' => [
'start' => '--n-tabs-title-justify-content: flex-start; --n-tabs-title-align-items: flex-start; --n-tabs-title-text-align: start;',
'center' => '--n-tabs-title-justify-content: center; --n-tabs-title-align-items: center; --n-tabs-title-text-align: center;',
'end' => '--n-tabs-title-justify-content: flex-end; --n-tabs-title-align-items: flex-end; --n-tabs-title-text-align: end;',
'{{WRAPPER}}' => '{{VALUE}}',
$this->end_controls_section();
$this->start_controls_section( 'section_tabs_responsive', [
'label' => esc_html__( 'Additional Settings', 'elementor' ),
$this->add_responsive_control(
'label' => esc_html__( 'Horizontal Scroll', 'elementor' ),
'type' => Controls_Manager::SELECT,
'description' => esc_html__( 'Note: Scroll tabs if they don’t fit into their parent container.', 'elementor' ),
'disable' => esc_html__( 'Disable', 'elementor' ),
'enable' => esc_html__( 'Enable', 'elementor' ),
'selectors_dictionary' => [
'disable' => '--n-tabs-heading-wrap: wrap; --n-tabs-heading-overflow-x: initial; --n-tabs-title-white-space: initial;',
'enable' => '--n-tabs-heading-wrap: nowrap; --n-tabs-heading-overflow-x: scroll; --n-tabs-title-white-space: nowrap;',
'{{WRAPPER}}' => '{{VALUE}}',
'frontend_available' => true,
'none' => esc_html__( 'None', 'elementor' ),
$excluded_breakpoints = [
foreach ( Plugin::$instance->breakpoints->get_active_breakpoints() as $breakpoint_key => $breakpoint_instance ) {
// Exclude the larger breakpoints from the dropdown selector.
if ( in_array( $breakpoint_key, $excluded_breakpoints, true ) ) {
$dropdown_options[ $breakpoint_key ] = sprintf(
/* translators: 1: Breakpoint label, 2: `>` character, 3: Breakpoint value. */
esc_html__( '%1$s (%2$s %3$dpx)', 'elementor' ),
$breakpoint_instance->get_label(),
$breakpoint_instance->get_value()
'label' => esc_html__( 'Breakpoint', 'elementor' ),
'type' => Controls_Manager::SELECT,
'description' => esc_html__( 'Note: Choose at which breakpoint tabs will automatically switch to a vertical (“accordion”) layout.', 'elementor' ),
'options' => $dropdown_options,
'prefix_class' => 'e-n-tabs-',
$this->end_controls_section();
$this->start_controls_section( 'section_tabs_style', [
'label' => esc_html__( 'Tabs', 'elementor' ),
'tab' => Controls_Manager::TAB_STYLE,
$this->add_responsive_control( 'tabs_title_space_between', [
'label' => esc_html__( 'Gap between tabs', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'{{WRAPPER}}' => '--n-tabs-title-gap: {{SIZE}}{{UNIT}}',
$this->add_responsive_control( 'tabs_title_spacing', [
'label' => esc_html__( 'Distance from content', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'{{WRAPPER}}' => '--n-tabs-gap: {{SIZE}}{{UNIT}}',
$this->start_controls_tabs( 'tabs_title_style' );
$this->start_controls_tab(
'label' => esc_html__( 'Normal', 'elementor' ),