Tooltip Dialog

Tooltip Dialog

A tooltip dialog is a dialog that pops up in response to a user action, near the current point of focus, similar to the way a tooltip does. It is usually intended to be modal, but this is not as strictly observed as it would normally be with a regular dialog. And though a tooltip often appears on focus or on hover, forcing a dialog to appear on focus or hover is not expected or advisable, because it would move the focus without any prior warning. It is better to allow the user to activate the dialog purposefully.



Turn on a screen reader to experience this example in action.

Attribute/Option Description
horizontalPlacing This attribute/option specifies the horizontal placement of the tooltip dialog box. The valid values are LEFT, RIGHT and CENTER. By default, the tooltip dialog is placed in the center.
verticalPlacing This attribute/option specifies the vertical placement of the tooltip dialog box. The valid values are TOP and BOTTOM. By default, the tooltip dialog is placed at the top.

HTML Source Code

<table class="data">
    <tr>
        <th width="150px" >Attribute/Option</th>
        <th>Description</th>
    </tr>
    <tr>
        <td><code>horizontalPlacing</code></td>
        <td>This attribute/option specifies the horizontal placement of the tooltip dialog box. 
            The valid values are LEFT, RIGHT and CENTER. 
            By default, the tooltip dialog is placed in the center.
        </td>
    </tr>
    <tr>
        <td><code>verticalPlacing</code></td>
        <td>This attribute/option specifies the vertical placement of the tooltip dialog box. 
            The valid values are TOP and BOTTOM. 
            By default, the tooltip dialog is placed at the top.
        </td>
    </tr>
</table>

<div class="container">
    <div class="panel-1">
      <label for="targetField">Recipient</label>
      <input type="text" id="targetField" />
      <button class="icon-button primary" id="targetFieldHelper" type="button" aria-label="Prepopulate recipient" >
        <span class="fa-solid fa-circle-info"></span></button>
    </div>
    <div role="dialog"  class="tooltip select-user" aria-hidden="true" id="tooltip-dialog-1" aria-label="Prepopulate Recipients"  >
        <form action="javascript:void(0)">
            <fieldset class="deque" tabindex="-1">
            <legend>Select a name:</legend>
            <label>Alice
                <input type="radio" name="contactList" value="Alice" checked="">
            </label>
            <label>Bob
                <input type="radio" name="contactList" value="Bob">
            </label>
            <label>Paul
                <input type="radio" name="contactList" value="Paul" aria-labelledby="radioLabel">
            </label>
            </fieldset>
            <p><button id="confirm" class="button primary">Confirm</button></p>
        </form>
    </div>    
</div>

JavaScript Source Code

var langText = {
    "errorNodeNotObject": "Node provided is not a DOM object.",
    "errorNoTooltip": "tooltip attribute 'data-tooltip' not defined.",
    "errorTriggerNotObject": "Trigger button provided is not a DOM object.",
    "errorTriggerNotButton": "Trigger button provided is not a Button object.",
    
}

class TooltipDialog {

    static LEFT = 0;
    static CENTER = 1;
    static RIGHT = 2;
    static TOP = 3;
    static BOTTOM = 4;


    constructor(domNode, triggerButton, options) {

        this.showOnFocus = true;
        this.horizontalPlacing = TooltipDialog.CENTER;
        this.verticalPlacing = TooltipDialog.TOP;
        
        if ( typeof options != "object" ) options = {};
        if( (typeof domNode != "object" ) && (typeof domNode.nodeName === "undefined") )
        {
            console.log(langText.errorNodeNotObject);
            return;
        }
        if( (typeof triggerButton != "object" ) && (typeof triggerButton.nodeName === "undefined") )
        {
            console.log(langText.errorTriggerNotObject);
            return;
        }
        if( triggerButton.nodeName.toLowerCase() !== "button" )
        {
            console.log(langText.errorTriggerNotButton);
            return;
        }
        
        //this.node = domNode;
        this.tooltip = domNode;
        this.triggerButton = triggerButton;
        this.hide();

        if(domNode.id.trim() == "")
            domNode.id = crypto.randomUUID();
        
        if ( typeof options != "object" ) options = {};
        var validValues = [TooltipDialog.LEFT, TooltipDialog.CENTER, TooltipDialog.RIGHT];
        if( (typeof options["horizontalPlacing"] === "number") && (validValues.includes(options["horizontalPlacing"]) ) )
             this.horizontalPlacing =  options["horizontalPlacing"];
        validValues = [TooltipDialog.TOP, TooltipDialog.BOTTOM];
        if( (typeof options["verticalPlacing"] === "number") && (validValues.includes(options["verticalPlacing"]) ) )
             this.verticalPlacing =  options["verticalPlacing"];
          
        window.addEventListener("resize", this.updatePosition.bind(this)); 
        triggerButton.addEventListener("click", this.show.bind(this));
        document.body.addEventListener("keydown", this.handleEscapeKey.bind(this) );

        var focusElements = domNode.querySelectorAll('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])');  
        if(focusElements.length > 0)
        {
            var firstFocusElement = focusElements[0];
            var lastFocusElement = focusElements[focusElements.length-1];
            this.firstFocusElement = firstFocusElement;
            this.lastFocusElement = lastFocusElement;
            lastFocusElement.addEventListener("keydown", function(event){
                var isTabPressed = event.key === 'Tab' || event.keyCode === 9;
                if(isTabPressed && !event.shiftKey)
                {
                    event.preventDefault();
                    firstFocusElement.focus();
                }
            });
            firstFocusElement.addEventListener("keydown", function(event){
                var isTabPressed = event.key === 'Tab' || event.keyCode === 9;
                if(isTabPressed && event.shiftKey )
                {
                    event.preventDefault();
                    lastFocusElement.focus();
                }
            });
        }

    }

    hide()
    {
        this.tooltip.classList.add("hidden");
        this.tooltip.classList.remove("visible");
        this.tooltip.setAttribute("aria-hidden", "true");
        this.triggerButton.focus();
    }

    show()
    {
        this.tooltip.classList.remove("hidden");
        this.tooltip.classList.add("visible");
        this.tooltip.setAttribute("aria-hidden", "false");
        var top = this.tooltip.parentElement.offsetTop+ this.tooltip.parentElement.offsetHeight;
        if( this.horizontalPlacing == TooltipDialog.LEFT )
            var left = this.tooltip.parentElement.offsetLeft+"px";
        if( this.horizontalPlacing == TooltipDialog.CENTER )
            var left = this.tooltip.parentElement.offsetLeft + ( this.tooltip.parentElement.offsetWidth - this.tooltip.offsetWidth)/2 + "px";
        if( this.horizontalPlacing == TooltipDialog.RIGHT )
            var left = this.tooltip.parentElement.offsetLeft + ( this.tooltip.parentElement.offsetWidth - (this.tooltip.offsetWidth)) + "px";    
        if( this.verticalPlacing == TooltipDialog.TOP)
            top = this.tooltip.parentElement.offsetTop - this.tooltip.offsetHeight;

        this.tooltip.style.top = top + "px";
        this.tooltip.style.left = left ;
        this.firstFocusElement.focus();
    }

    handleEscapeKey(event)
    {
        if(event.key == "Escape")
            this.hide();
    }

    updatePosition()
    {
        if(this.tooltip.classList.contains("visible")  )
            this.show();
    }



}


var options = {};
//options.horizontalPlacing = TooltipDialog.LEFT;
//options.verticalPlacing = TooltipDialog.BOTTOM;
var tooltipDialog1 = new TooltipDialog(document.getElementById("tooltip-dialog-1"), document.getElementById("targetFieldHelper"), options);
window.addEventListener('load', function () {  
    document.getElementById("confirm").addEventListener("click", selectUser);
});

function selectUser()
{
    var user = document.querySelector('input[name=contactList]:checked').value;
    document.getElementById("targetField").value = user;
    tooltipDialog1.hide();
}

CSS Source Code

.button { 
padding: 9px 12px; 
display: inline-block; 
border: 2px solid gray;
margin: 2px;
}

.icon-button { 
padding: 4px 7px; 
border: 2px solid gray;
font-size: 18px;
}

.button:hover{
    background-color: #aed9ee;
}
.button:focus, .icon-button:focus {
    outline: 2px solid blue;
}
.primary {
background-color: #0464b3;
color: white;
}

.container {
    border: 2px solid #ccc;
    padding: 20px;
    margin-top: 100px;
    max-width: 600px;
}    
.panel-1{
    padding: 5px;
}
.panel-1 label { margin: 5px;}
.panel-1 input { 
    vertical-align: middle;    
    width: 276px;
    max-width: 70%;
    padding: 7px 10px;
    border: 1px solid #000;
}
.panel-1 button { margin: 5px; vertical-align: middle;}

.tooltip label { margin: 5px;}
.tooltip-wrapper {
position: relative;
}
.tooltip {
box-sizing: border-box;
font-size: 13px;
position: absolute;
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.3);
min-width: 104px;

margin-right: -340px;
padding: 8px 6px;
line-height: 16px;
z-index: 70;
top: -25px;
left: 80%;
}
.select-user { 
    padding: 10px 30px 0px 10px;
    width: 350px;
}

.hidden { display: none;}
.visible { display: block;}

@media screen and (max-width: 500px) {
    .container {
        padding: 5px;
    }  
    .select-user { 
        padding: 2px 6px 0px 2px;
        width: 260px;
    }
    .panel-1 label {
        display: inline-block;
        width: 100%;
    }  
}
@media screen and (max-width: 300px) {
    .panel-1 input { 
        width: 236px;
        max-width: 60%;
    }
        
}
        

Copy and Paste Full Page Example