Tooltip

Tooltip

Overview

A tooltip provides extra information about a form field, a link, a button, or other focusable element. It must be triggered by both focus and hover events and remains on the screen as long as the trigger has the focus. The focus does not move to the tooltip.



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

Attribute/Option Description
styleClass This attribute/option specifies the CSS class to be used for customized styling of the tooltip. If this option is not specified, a default tooltip styling is used.
showOnFocus This attribute/option (boolean value, true or false) specifies if the tooltip should be displayed if the element has a focus. By default, this option is enabled.
keepTooltipOnMouseOver This attribute/option (boolean value, true or false) specifies if the tooltip should be displayed when a user moves the mouse over the tooltip. By default, this option is enabled.

Link 1

Link 2

HTML Source Code

<div class="dqu-example">
<table class="data">
    <tr>
        <th width="150px" >Attribute/Option</th>
        <th>Description</th>
    </tr>
    <tr>
        <td><code>styleClass</code></td>
        <td>This attribute/option specifies the CSS class to be used for customized styling of the tooltip.
            If this option is not specified, a default tooltip styling is used.
        </td>
    </tr>
    <tr>
        <td><code>showOnFocus</code></td>
        <td>This attribute/option (boolean value, <code>true</code> or <code>false</code>) specifies if the tooltip should be displayed if the element has a focus.
            By default, this option is enabled.
        </td>
    </tr>
    <tr>
        <td><code>keepTooltipOnMouseOver</code></td>
        <td>This attribute/option (boolean value, <code>true</code> or <code>false</code>) specifies if the tooltip should be displayed when a user moves the mouse over the tooltip.
            By default, this option is enabled.
        </td>
    </tr>    
</table>
<br/>
<p>
    <label for="nameInput">First Name</label>
    <input id="nameInput" type="text" autocomplete="given-name" data-tooltip="Your given name"
      class="deque-input">
</p>
<p>
    <label for="lastInput">Last Name</label>
    <input id="lastInput" type="text"
    autocomplete="family-name"
      data-tooltip="Your surname or family name. This tooltip has a custom styling." class="deque-input">
</p>

<p>
    <a id="demo-link1" href="javascript:void(0);"
      data-tooltip="Tooltips can contain markup such as
      <strong>&amp;lt;strong&amp;gt;</strong> and <em>&amp;lt;em&amp;gt;</em>
      and even images:<br> <img src='https://dequeuniversity.com/assets/images/module-aria/sun.png'
      alt='Emoji smiling sun' height='24' width='24'>">
        Link 1
    </a>
</p>
<p>
    <a id="demo-link2" href="javascript:void(0);"
      data-tooltip="This is a non-functional demo
      link with a tooltip. This tooltip has a custom styling.">
        Link 2
    </a>
</p>

<p>
    <button id="demo-button1" class="deque-button"
      data-tooltip="This particular tooltip is longer than the others,
      and spans several rows. The height is calculated automatically by the
      script and the offset is applied appropriately. It is possible to create
      long tooltips, but it is NOT recommended. The aria-describedby attribute
      does not allow screen reader users to pause in the middle of a tooltip
      and start again where they left off. They have to focus again on
      the element and listen to the whole tooltip from the beginning.">
        Button 1
    </button>
    <button id="demo-button2" class="deque-button"
      data-tooltip="This is a non-functional demo link with a tooltip. This tooltip has a custom styling.">
        Button 2
    </button>
</p>
<p>
    <button id="demo-button3" class="deque-button" data-tooltip="This is a non-functional demo button with a tooltip">Button 3</button>
</p>
</div>

JavaScript Source Code

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

class Tooltip {

    static elements = [];

    constructor(domNode, options) {

        var self = this;
        this.showOnFocus = true;
        this.keepTooltipOnMouseOver = true;
        this.tooltipMouseOver = false;

        if ( typeof options != "object" ) options = {};
        if( (typeof domNode != "object" ) && (typeof domNode.nodeName === "undefined") )
        {
            console.log(langText.errorNodeNotObject);
            return;
        }
        var tooltipText = domNode.getAttribute("data-tooltip");
        if( !tooltipText )
        {
            console.log(langText.errorNoTooltip);
            return;
        }
        var wrapperSpan = document.createElement("span");
        domNode.parentNode.replaceChild(wrapperSpan, domNode);
        wrapperSpan.appendChild(domNode);
        var tooltipSpan = document.createElement("span");
        wrapperSpan.appendChild(tooltipSpan);
        wrapperSpan.classList.add("tooltip-wrapper");
        tooltipSpan.classList.add("tooltip");
        tooltipSpan.setAttribute("role", "tooltip");
        tooltipSpan.id = crypto.randomUUID();
        domNode.setAttribute("aria-describedby", tooltipSpan.id);
        this.node = domNode;
        this.tooltip = tooltipSpan;
        
        if ( typeof options != "object" ) options = {};
        if( (typeof options["styleClass"] === "string") && (options["styleClass"].trim() != "" ) )
            tooltipSpan.classList.add(options["styleClass"]);
        if(typeof options["showOnFocus"] === "boolean")
            this.showOnFocus = options["showOnFocus"];
        if(typeof options["keepTooltipOnMouseOver"] === "boolean")
            this.keepTooltipOnMouseOver = options["keepTooltipOnMouseOver"];
            

        this.hideTooltip();
        var labelSpan = document.createElement("span");
        labelSpan.setAttribute("aria-label", "Tooltip :");
        labelSpan.setAttribute("role", "tooltip");
        tooltipSpan.appendChild(labelSpan);
        tooltipSpan.insertAdjacentHTML("beforeend", tooltipText);

        this._boundShowTooltip = this.showTooltip.bind(this);
        this._boundMouseOut = this.mouseOutHandler.bind(this);
        this._boundFocus = this.showTooltip.bind(this);
        this._boundBlur = function() { if(!self.tooltipMouseOver) self.hideTooltip(); };
        this._boundTooltipOver = function() { self.tooltipMouseOver = true; self.showTooltip(); };
        this._boundTooltipMove = function() { self.tooltipMouseOver = true; self.showTooltip(); };
        this._boundTooltipOut = this.hideTooltip.bind(this);

        domNode.addEventListener("mouseover", this._boundShowTooltip);
        domNode.addEventListener("mousemove", this._boundShowTooltip);
        domNode.addEventListener("mouseout", this._boundMouseOut);
        document.body.addEventListener("keydown", Tooltip.handleEscapeKey);

        if (this.showOnFocus)
        {
            domNode.addEventListener("focus", this._boundFocus);
            domNode.addEventListener("blur", this._boundBlur);
        }
        if(this.keepTooltipOnMouseOver)
        {
            this.tooltip.addEventListener("mouseover", this._boundTooltipOver);
            this.tooltip.addEventListener("mousemove", this._boundTooltipMove);
            this.tooltip.addEventListener("mouseout", this._boundTooltipOut);
        }
        Tooltip.elements.push(this);    
    }

    hideTooltip(escapeKey)
    {
        if((document.activeElement == this.node) && (escapeKey !== true) )  return;
        this.tooltip.classList.add("hidden");
        this.tooltip.classList.remove("visible");
        this.tooltip.setAttribute("aria-hidden", "true");
    }
    showTooltip(event)
    {
        this.tooltip.classList.add("visible");
        this.tooltip.classList.remove("hidden");
        this.tooltip.setAttribute("aria-hidden", "false");
        this.tooltip.style.top = (-10 - this.tooltip.offsetHeight) + "px";
        
    }
    mouseOutHandler(event)
    {
        var self=this;
        var id = setTimeout( function(){ 
            if( self.tooltipMouseOver)
            {
                self.tooltipMouseOver = false;
                return;
            }
            self.hideTooltip();
        }, 1000);

    }

    static handleEscapeKey(event)
    {
        if(event.key == "Escape")
        {
            var elements = Tooltip.elements;
            for(var i =0; i < elements.length; i++)
                elements[i].hideTooltip(true);
        }
    }

    destroy()
    {
        this.node.removeEventListener("mouseover", this._boundShowTooltip);
        this.node.removeEventListener("mousemove", this._boundShowTooltip);
        this.node.removeEventListener("mouseout", this._boundMouseOut);
        this.node.removeEventListener("focus", this._boundFocus);
        this.node.removeEventListener("blur", this._boundBlur);
        this.tooltip.removeEventListener("mouseover", this._boundTooltipOver);
        this.tooltip.removeEventListener("mousemove", this._boundTooltipMove);
        this.tooltip.removeEventListener("mouseout", this._boundTooltipOut);
        var index = Tooltip.elements.indexOf(this);
        if (index > -1) Tooltip.elements.splice(index, 1);
        if (Tooltip.elements.length === 0) {
            document.body.removeEventListener("keydown", Tooltip.handleEscapeKey);
        }
    }

}    
 

var tooltips = {};
var itemsToTip = document.querySelectorAll('[data-tooltip]');
var options = {};
for (var i = 0; i < itemsToTip.length; i++) {
    options.styleClass = null;   
 if( ((i+1) % 2) == 0 )
    options.styleClass = "tooltip-custom-1";
  tooltips[itemsToTip[i].id] = new Tooltip(itemsToTip[i], options);
}

CSS Source Code

/*
  Tooltip — Restyled
  Deque University ARIA Component
*/

:root {
  --dqu-interactive: #2e5f7a;
  --dqu-interactive-hover: #3a7a9a;
  --dqu-interactive-light: rgba(46, 95, 122, 0.08);
  --dqu-bg-primary: #fcfaf8;
  --dqu-bg-secondary: #f6f3ed;
  --dqu-border-secondary: #8c827d;
  --dqu-text-primary: #21201e;
  --dqu-font-family: "Noto Sans", sans-serif;
}

/* Tooltip wrapper */
.tooltip-wrapper {
  position: relative;
  display: inline-block;
}

/* Tooltip bubble — light theme using new Deque palette */
.tooltip {
  box-sizing: border-box;
  font-family: var(--dqu-font-family);
  font-size: 0.9375rem;
  line-height: 1.5;
  position: absolute;
  background: #ffffff;
  color: var(--dqu-text-primary);
  border: 1px solid var(--dqu-border-secondary);
  border-radius: 6px;
  width: max-content;
  min-width: 104px;
  max-width: 360px;
  padding: 8px 12px;
  z-index: 70;
  left: 50%;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

/* Arrow — pointing down to the trigger; uses two triangles to draw
   a 1px border around the arrow that matches the bubble border. */
.tooltip::before,
.tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 16px;
  width: 0;
  height: 0;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
}
.tooltip::before {
  border-top: 8px solid var(--dqu-border-secondary);
}
.tooltip::after {
  margin-top: -1px;
  border-top: 8px solid #ffffff;
}

/* Custom tooltip style variant — slightly tinted background to
   distinguish it from the default light tooltip. */
.tooltip-custom-1 {
  font-size: 0.9375rem;
  font-family: var(--dqu-font-family);
  padding: 12px 16px;
  background-color: var(--dqu-bg-secondary);
  color: var(--dqu-text-primary);
  border: 1px solid var(--dqu-border-secondary);
}

.tooltip-custom-1::after {
  border-top-color: var(--dqu-bg-secondary);
}

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

/* Form elements */
.dqu-example label {
  font-family: var(--dqu-font-family);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--dqu-text-primary);
  display: block;
  margin-bottom: 4px;
}

input.deque-input {
  padding: 8px 12px;
  border: 1px solid var(--dqu-border-secondary);
  border-radius: 8px;
  font-family: var(--dqu-font-family);
  font-size: 1rem;
  background: var(--dqu-bg-primary);
  color: var(--dqu-text-primary);
}

input.deque-input:focus {
  outline: 2px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
  border-color: var(--dqu-interactive);
}

/* Links */
.dqu-example a {
  color: var(--dqu-interactive);
  text-decoration: underline;
  font-family: var(--dqu-font-family);
}

.dqu-example a:hover {
  background-color: var(--dqu-interactive-light);
}

.dqu-example a:focus {
  outline: 3px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
}

/* Buttons */
button.deque-button {
  padding: 8px 14px !important;
  font-family: var(--dqu-font-family);
  font-size: 0.875rem;
  font-weight: 600;
  margin: 4px;
  border-radius: 9999px !important;
  cursor: pointer;
  border: 1px solid var(--dqu-interactive) !important;
  background: #ffffff;
  color: var(--dqu-interactive);
  transition: background-color 0.15s ease;
}

button.deque-button:hover,
button.deque-button:focus {
  background-color: var(--dqu-interactive-light) !important;
  outline: 3px solid var(--dqu-interactive) !important;
  outline-offset: 2px;
  border: 1px solid var(--dqu-interactive) !important;
  padding: 8px 14px !important;
  margin: 4px !important;
  color: var(--dqu-interactive) !important;
}

/* Data table */
table.data {
  border-collapse: collapse;
  font-family: var(--dqu-font-family);
  font-size: 0.9375rem;
  margin-bottom: 32px;
}

table.data th,
table.data td {
  border: 1px solid var(--dqu-border-secondary);
  padding: 10px 12px;
  text-align: left;
  color: var(--dqu-text-primary);
}

table.data th {
  background: var(--dqu-bg-secondary);
  font-weight: 600;
}

.dqu-example p {
  font-family: var(--dqu-font-family);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--dqu-text-primary);
}

@media screen and (max-width: 700px) {
  .tooltip {
    left: 10%;
    max-width: 200px;
  }
}

Copy and Paste Full Page Example