Home / Admin / 3 Dots for popup text in WordPress
Duplicate Snippet

Embed Snippet on Your Site

3 Dots for popup text in WordPress

With this snippet 3 highlighted dots appear when you create small items of text that you want to popup.
This is good because sometimes a small explanation helps. You may not want to create loads of explanations in brackets - parentheses.
The shortcode that allows insertion is [[fn]]...[[/fn]] - so in practice: [[fn]]some popup text I want to offer[[/fn]]
Those familiar with Espanso can create triggers easily. I use :fn as my trigger and out comes [[fn]][[/fn]] - the cursor even snaps right where I need to insert my popup text.

Code Preview
php
<?php
/**
 * Footnote popup — combined CSS + JS + content filter
 * Uses [[fn]]...[[/fn]] syntax — avoids:
 *  - WordPress shortcode auto-block conversion in Gutenberg ([fn]...[/fn] would trigger it)
 *  - espanso's {{ }} variable syntax clash ({{fn}}...{{/fn}} would trigger it)
 * Usage: [[fn]]Your footnote text here[[/fn]]
 */
function custom_fn_content_filter( $content ) {
    // Only bother if the marker is present
    if ( strpos( $content, '[[fn]]' ) === false ) {
        return $content;
    }
    // Make sure the footer assets get printed once
    add_action( 'wp_footer', 'custom_fn_footer_assets' );
    // Convert [[fn]]text[[/fn]] into the trigger span
    $content = preg_replace_callback(
        '/\[\[fn\]\](.*?)\[\[\/fn\]\]/s',
        function( $matches ) {
            return '<span class="fn-trigger" data-fn="' . esc_attr( $matches[1] ) . '"></span>';
        },
        $content
    );
    return $content;
}
add_filter( 'the_content', 'custom_fn_content_filter' );
function custom_fn_footer_assets() {
    // Guard: only print the CSS/JS once per page, however many footnotes appear
    static $already_printed = false;
    if ( $already_printed ) {
        return;
    }
    $already_printed = true;
    ?>
    <style>
    /* 3-Dot Footnote Trigger */
    .fn-trigger {
        position: relative;
        display: inline-block;
        background: #FFD700; /* Prominent yellow background */
        color: #0033CC; /* Prominent blue dots */
        border-radius: 4px;
        padding: 2px 6px;
        margin: 0 2px;
        cursor: pointer;
        font-size: inherit;
        line-height: 1;
        vertical-align: middle;
        font-weight: bold;
    }
    .fn-trigger::after {
        content: "•••";
        letter-spacing: 3px;
    }
    /* Show popup on Hover OR when Active (clicked) */
    .fn-trigger:hover .fn-popup,
    .fn-trigger.active .fn-popup {
        opacity: 1;
        visibility: visible;
        transform: translateX(-50%) translateY(-5px);
    }
    /* Footnote Popup - pale light blue */
    .fn-popup {
        position: absolute;
        bottom: 100%;
        left: 50%;
        transform: translateX(-50%) translateY(0);
        width: 300px;
        max-width: 90vw;
        background: #e3f2fd;
        border: 1px solid #bbdefb;
        border-radius: 6px;
        padding: 12px 16px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
        font-size: 14px;
        line-height: 1.5;
        color: #333;
        z-index: 9999;
        opacity: 0;
        visibility: hidden;
        transition: all 0.2s ease;
        pointer-events: none;
    }
    .fn-trigger:hover .fn-popup,
    .fn-trigger.active .fn-popup {
        pointer-events: auto;
    }
    .fn-popup::after {
        content: "";
        position: absolute;
        top: 100%;
        left: 50%;
        transform: translateX(-50%);
        border: 6px solid transparent;
        border-top-color: #e3f2fd;
    }
    </style>
    <script>
    (function() {
        function setupFootnotes() {
            var triggers = document.querySelectorAll('.fn-trigger');
            for (var i = 0; i < triggers.length; i++) {
                var trigger = triggers[i];
                // Prevent double-initialisation
                if (trigger.getAttribute('data-fn-ready')) continue;
                trigger.setAttribute('data-fn-ready', 'true');
                // Create the popup
                var popup = document.createElement('div');
                popup.className = 'fn-popup';
                popup.innerHTML = trigger.getAttribute('data-fn');
                trigger.appendChild(popup);
                // Handle click
                trigger.addEventListener('click', function(e) {
                    e.stopPropagation();
                    var isActive = this.classList.contains('active');
                    // Close all other popups first
                    var allTriggers = document.querySelectorAll('.fn-trigger');
                    for (var j = 0; j < allTriggers.length; j++) {
                        allTriggers[j].classList.remove('active');
                    }
                    // Toggle this one
                    if (!isActive) {
                        this.classList.add('active');
                    }
                });
            }
        }
        // Run immediately (footer placement)
        setupFootnotes();
        // Also run on window load just in case
        window.addEventListener('load', setupFootnotes);
        // Close popups when clicking anywhere else on the page
        document.addEventListener('click', function(e) {
            if (!e.target.closest('.fn-trigger')) {
                var allTriggers = document.querySelectorAll('.fn-trigger');
                for (var j = 0; j < allTriggers.length; j++) {
                    allTriggers[j].classList.remove('active');
                }
            }
        });
    })();
    </script>
    <?php
}

Comments

Add a Comment