Home / Admin / Add Custom CSS Indicators & Nested Badges to the Elementor Structure/Navigator
Duplicate Snippet

Embed Snippet on Your Site

Add Custom CSS Indicators & Nested Badges to the Elementor Structure/Navigator

OVERVIEW
This enhanced snippet upgrades the Elementor editing experience by adding visual indicators directly inside the Structure/Navigator panel. Building upon Stefan Radu’s original custom CSS indicator concept, this version introduces hierarchical parent badges and full color/style configuration.

KEY FEATURES

Direct CSS Indicators:
Displays a clean purple dot next to any specific widget, container, or section that has Custom CSS applied.

Nested Parent Badges:
Highlights parent containers or sections with a distinct darker purple badge containing a **(+)** symbol if they hold nested child elements with Custom CSS.

Configurable Styling:
Easily customize colors, sizes, hover scales, and opacity via top-level configuration variables in the script.

Real-Time Tracking:
Dynamically updates indicators as you build, edit, or reorganize elements in the Elementor editor.

CREDITS
Original concept and blue dot indicator by Stefan Radu (Romania).
Container check, nested `(+)` badge, and configuration framework by Lars Bregendahl Bro (Denmark).

Code Preview
php
<?php
/* ====================================================================================
 * 
 * Add Custom CSS Indicators & Nested Badges to the Elementor Structure/Navigator
 * 
 * Stefan Radu (Romania) created the original snippet that adds a blue (now purple) dot 
 * next to Elementor Structure/Navigator items with Custom CSS, making them instantly 
 * identifiable during editing.
 * 
 * Lars Bregendahl Bro (Denmark) expanded it with a container check that uses 
 * a plus badge (+) to highlight parent elements holding nested items with Custom CSS, 
 * and added top-level design configuration variables for individual styling control.
 ==================================================================================== */
add_action('elementor/editor/before_enqueue_scripts', function() {
    if (!isset($_GET['action']) || $_GET['action'] !== 'elementor') {
        return;
    }
    wp_add_inline_script('elementor-editor', '
        jQuery(function($) {
            // ================================================
            // INDIVIDUAL DESIGN VARIABLES FOR EACH DOT TYPE
            // ================================================
            const CONFIG = {
                // 1. Settings for custom CSS (The dot) - Light Purple
                customCss: {
                    color: "#9b51e0",         // Color of the dot
                    size: "10px",             // Size of the dot
                    opacity: "0.85",          // Default opacity
                    hoverScale: "1.15"        // Zoom effect on hover
                },
                // 2. Settings for child elements with CSS (Badge with +) - Darker Purple
                parentCss: {
                    color: "#6b21a8",         // Color of the circle (background)
                    size: "10px",             // Size of the circle
                    opacity: "0.85",          // Default opacity
                    hoverScale: "1.15",       // Zoom effect on hover
                    
                    // Settings for the symbol (+) inside the circle
                    text: "+",                // The symbol to display
                    textColor: "#ffffff",     // Color of the symbol
                    textSize: "10px",         // Font size
                    fontWeight: "900"         // Font weight (e.g., normal, bold, 600)
                },
                // General settings
                animationSpeed: "0.2s"         // Speed of the hover effect
            };
            // ==========================================
            function checkCustomCSS(element) {
                try {
                    var container = element.getContainer?.() || element;
                    var model = container?.model || element.model;
                    
                    if (!model) {
                        return false;
                    }
                    var settings = model.get("settings");
                    return settings.get("custom_css") && settings.get("custom_css").trim() !== "";
                } catch (error) {
                    return false;
                }
            }
            function hasChildrenWithCustomCSS(element) {
                try {
                    var model = element.getContainer?.()?.model || element.model;
                    if (!model) return false;
                    if (model.get("elType") !== "container" && model.get("elType") !== "section") {
                        return false;
                    }
                    var hasChildCSS = false;
                    
                    function scanChildren(childModel) {
                        var settings = childModel.get("settings");
                        if (settings && settings.get("custom_css") && settings.get("custom_css").trim() !== "") {
                            hasChildCSS = true;
                            return;
                        }
                        
                        var children = childModel.get("elements");
                        if (children && children.models) {
                            children.models.forEach(function(subChild) {
                                if (!hasChildCSS) scanChildren(subChild);
                            });
                        }
                    }
                    var elements = model.get("elements");
                    if (elements && elements.models) {
                        elements.models.forEach(function(child) {
                            if (!hasChildCSS) scanChildren(child);
                        });
                    }
                    return hasChildCSS;
                } catch (error) {
                    return false;
                }
            }
            function updateNavigatorIcon(element, hasCustomCSS, hasChildCSS) {
                try {
                    var model = element.getContainer?.()?.model || element.model;
                    if (!model) return;
                    setTimeout(function() {
                        var $element = $(".elementor-navigator__element[data-model-cid=\"" + model.cid + "\"]");
                        var $titleContainer = $element.find("> .elementor-navigator__item > .elementor-navigator__element__title");
                        
                        $titleContainer.find(".elementor-navigator__element__css-icon, .elementor-navigator__element__parent-css-icon").remove();
                        
                        var iconsHtml = "";
                        if (hasCustomCSS) {
                            iconsHtml += \'<div class="elementor-navigator__element__css-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="\' + CONFIG.customCss.color + \'" width="\' + CONFIG.customCss.size + \'" height="\' + CONFIG.customCss.size + \'" style="margin: 0 3px"><circle cx="256" cy="256" r="256"/></svg></div>\';
                        }
                        if (hasChildCSS && !hasCustomCSS) {
                            iconsHtml += \'<div class="elementor-navigator__element__parent-css-icon"><span class="badge-symbol">\' + CONFIG.parentCss.text + \'</span></div>\';
                        }
                        if (iconsHtml !== "") {
                            $titleContainer.find("> .elementor-navigator__element__title__text").after(iconsHtml);
                        }
                    }, 100);
                } catch (error) {
                    console.debug("Error updating icon:", error);
                }
            }
            function findAllElements(view) {
                var elements = [view];
                if (view.children) {
                    view.children.each(function(childView) {
                        elements = elements.concat(findAllElements(childView));
                    });
                }
                return elements;
            }
            function checkFromFrontendConfig(element) {
                try {
                    var model = element.getContainer?.()?.model || element.model;
                    if (!model?.cid) return false;
                    
                    return window.elementorFrontend?.config?.elements?.data[model.cid]?.attributes?.custom_css?.trim();
                } catch (error) {
                    return false;
                }
            }
            var style = document.createElement("style");
            style.textContent = `
                .elementor-navigator__element__css-icon {
                    display: inline-flex;
                    align-items: center;
                    margin-left: 4px;
                    opacity: ` + CONFIG.customCss.opacity + `;
                }
                .elementor-navigator__element__css-icon:hover {
                    opacity: 1;
                    transform: scale(` + CONFIG.customCss.hoverScale + `);
                    transition: all ` + CONFIG.animationSpeed + ` ease;
                }
                .elementor-navigator__element__parent-css-icon {
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    margin-left: 5px;
                    width: ` + CONFIG.parentCss.size + `;
                    height: ` + CONFIG.parentCss.size + `;
                    background-color: ` + CONFIG.parentCss.color + `;
                    border-radius: 50%;
                    box-shadow: 0 0 2px rgba(0,0,0,0.3);
                    opacity: ` + CONFIG.parentCss.opacity + `;
                }
                .elementor-navigator__element__parent-css-icon .badge-symbol {
                    color: ` + CONFIG.parentCss.textColor + `;
                    font-size: ` + CONFIG.parentCss.textSize + `;
                    font-weight: ` + CONFIG.parentCss.fontWeight + `;
                    line-height: 1;
                }
                .elementor-navigator__element__parent-css-icon:hover {
                    opacity: 1;
                    transform: scale(` + CONFIG.parentCss.hoverScale + `);
                    transition: all ` + CONFIG.animationSpeed + ` ease;
                }
            `;
            document.head.appendChild(style);
            elementor.channels.editor.on("editor:element:style:update", function(view) {
                if (!view) return;
                var hasCSS = checkCustomCSS(view);
                var hasChildCSS = hasChildrenWithCustomCSS(view);
                updateNavigatorIcon(view, hasCSS, hasChildCSS);
            });
            elementor.channels.editor.on("change", function() {
                var selectedElement = elementor.selection.getElements()[0];
                if (selectedElement) {
                    var hasCSS = checkCustomCSS(selectedElement);
                    var hasChildCSS = hasChildrenWithCustomCSS(selectedElement);
                    updateNavigatorIcon(selectedElement, hasCSS, hasChildCSS);
                }
            });
            elementor.hooks.addAction("panel/open_editor/widget", function(panel, model, view) {
                setTimeout(function() {
                    var hasCSS = checkCustomCSS(view);
                    var hasChildCSS = hasChildrenWithCustomCSS(view);
                    updateNavigatorIcon(view, hasCSS, hasChildCSS);
                }, 100);
            });
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (mutation.type === "childList" && window.elementorFrontend?.config?.elements?.data) {
                        var mainView = elementor.getPreviewView();
                        if (mainView) {
                            var elements = findAllElements(mainView);
                            elements.forEach((element) => {
                                const hasCSS = checkCustomCSS(element) || checkFromFrontendConfig(element);
                                const hasChildCSS = hasChildrenWithCustomCSS(element);
                                if (hasCSS || hasChildCSS) {
                                    updateNavigatorIcon(element, hasCSS, hasChildCSS);
                                }
                            });
                        }
                    }
                });
            });
            function startObserving() {
                const navigator = document.querySelector("#elementor-navigator");
                if (navigator) {
                    observer.observe(navigator, {
                        childList: true,
                        subtree: true
                    });
                } else {
                    setTimeout(startObserving, 500);
                }
            }
            elementor.on("preview:loaded", () => {
                startObserving();
            });
        });
    ');
});

Comments

Add a Comment