v1.6.x-dev NDS IQ v6

Tooltip - National Design System

A click-activated balloon that reveals contextual guidance, definitions, or hints next to the term it relates to — with opt-in hover activation, a status-colored icon chip, and smart viewport positioning.

Help Tooltip

Click the icon trigger to reveal a balloon with a title, message, and leading icon chip. Click again, click outside, scroll, or press Escape to dismiss. Add data-tooltip-hover to open on hover and visible keyboard focus instead.

Click the help icon below
<span class="nds-tooltip"> <button type="button" class="nds-tooltip-trigger" aria-label="What is this?"> <span class="nds-feedback nds-sm" data-status="help"> <span class="nds-feedback-icon"> <i class="nds-icon" aria-hidden="true"></i> </span> </span> </button> <div class="nds-tooltip-balloon" hidden> <span class="nds-feedback nds-sm" data-status="help"> <span class="nds-feedback-icon"> <i class="nds-icon" aria-hidden="true"></i> </span> </span> <span class="nds-tooltip-body"> <span class="nds-tooltip-title">Tooltip title</span> <p class="nds-tooltip-message">Max width of tooltips is 240px - text will wrap automatically</p> </span> </div> </span>

Declarative Markup

Skip the inner DOM. Put data-tooltip-title and data-tooltip-message on a .nds-tooltip element. If the element is empty, the JS generates a chip trigger. If it already has text or children, the element itself acts as the trigger and only the balloon is generated.

Empty wrapper — chip trigger is generated
<span class="nds-tooltip" data-tooltip-title="Declarative tooltip" data-tooltip-message="This balloon was generated from data-tooltip-title/message by the JS."></span>
Element with content — element itself is the trigger

The National ID field is required.

<p> The <span class="nds-tooltip nds-term" data-tooltip-hover data-tooltip-message="A 10-digit identifier issued to Saudi citizens by the Ministry of Interior.">National ID</span> field is required. </p>

Message Only

Omit the title when a single short sentence is enough. The icon chip can also be dropped for a minimal body-only balloon.

Body-only tooltip
<span class="nds-tooltip"> <button type="button" class="nds-tooltip-trigger" aria-label="More info"> <span class="nds-feedback nds-sm" data-status="help"> <span class="nds-feedback-icon"> <i class="nds-icon" aria-hidden="true"></i> </span> </span> </button> <div class="nds-tooltip-balloon" hidden> <span class="nds-tooltip-body"> <p class="nds-tooltip-message">Saudi residents can enter either their National ID or their Iqama number.</p> </span> </div> </span>

Built-in Features

Auto-initialization

Every .nds-tooltip in the DOM wires itself up on page load. Interaction handlers, aria-describedby, and unique IDs are applied automatically.

Declarative Markup

Add data-tooltip-title and data-tooltip-message to an empty .nds-tooltip and the JS generates the trigger chip and balloon for you.

Smart Positioning

Balloons use fixed positioning so they escape clipping parents. They flip above the trigger when there is not enough room below and clamp horizontally to stay fully visible.

Status-Driven Icon

The feedback chip picks up its glyph and color from data-status: help, info, success, warning, error, or neutral.

Theme-Aware Colors

Balloon surface, heading, and body text all swap to the inverse token set when the page is in dark theme, with no markup changes required.

Multiple Dismissal Paths

Close by clicking the trigger again, clicking anywhere outside, pressing Escape, or scrolling the page. Hover-enabled tooltips also close when the pointer leaves. Only one tooltip stays open at a time.

Programmatic Control

Open, close, and observe any tooltip through the NDS.Tooltip API or the instance attached to each element.

Auto-Linked Description

Each balloon gets a unique id and the trigger's aria-describedby points to it, so screen readers announce the content when focus lands on the trigger.

Usage Guidelines

Best Practices

  • Use tooltips to clarify unfamiliar terms or offer short, optional context next to the element they explain (form field labels, table headers, policy terms)
  • For simple help text, prefer the declarative shortcut: put data-tooltip-title and data-tooltip-message on an empty .nds-tooltip and let the JS build the chip and balloon. Write the full markup only when you need rich HTML or custom content inside the body
  • Use the help status for advisory tips, info for neutral clarification, warning or error to call attention to a caveat next to a field
  • Place the trigger immediately adjacent to the term it describes so the relationship is obvious, not buried in a label row
  • Do not put essential instructions inside a tooltip: anything a user must read to complete the task belongs in the visible label, placeholder, or hint text
  • Do not wrap a link or navigation element as the trigger: clicking would both follow the link and try to toggle the tooltip. Place the tooltip next to the link instead
  • Prefer an Alert for non-dismissable status messages and a Modal for content that requires a user decision
  • Keep the message to one or two sentences. The balloon clamps at 240px by default and wraps text automatically
  • Include a short aria-label on the trigger button so screen reader users hear what the tooltip explains even before opening it
  • If many tooltips cluster in a dense form, consider moving the content into inline helper text: excessive clicking breaks flow for keyboard and touch users

Structure

A tooltip has three structural parts inside a <span class="nds-tooltip"> root.

ClassRole
nds-tooltipInline root wrapping the trigger and the balloon. JS toggles data-state="open" on this element while the balloon is visible.
nds-termOpt-in modifier on the root for inline text triggers: cursor:help and a dotted underline mark the annotated term (as in the "National ID" example). Without it the trigger keeps its own affordance — chip, button, or custom.
nds-tooltip-triggerThe clickable element. A <button> wrapping an .nds-feedback-icon chip is the canonical pattern. Omit this element when the root's own text content is the trigger (declarative text-trigger mode, as in the "National ID" example).
nds-tooltip-balloonThe floating panel. Marked hidden by default; JS toggles the attribute on open.
nds-tooltip-bodyText container inside the balloon. Sits beside the optional leading icon chip.
nds-tooltip-titleOptional bold heading placed at the top of the body stack.
nds-tooltip-messageThe body paragraph text.

Data Attributes

AttributeDescription
data-tooltip-titleDeclarative shortcut. Set on .nds-tooltip to have the JS generate the balloon title. Skipped when an explicit .nds-tooltip-balloon child is present.
data-tooltip-messageDeclarative shortcut. Set on .nds-tooltip to have the JS generate the balloon message paragraph. Either title or message (or both) is required for auto-generation to run.
data-tooltip-statusDeclarative shortcut. Set on .nds-tooltip to control the generated chips' status (defaults to help). Values match data-status.
data-tooltip-hoverOpt-in on .nds-tooltip: the balloon opens on mouse hover and visible keyboard focus instead of click. The optional value is the hover-open delay in milliseconds (e.g. data-tooltip-hover="500"; bare attribute = 120) — keyboard focus and touch always open immediately. Mouse clicks then just dismiss (the trigger's own action proceeds); touch keeps tap-to-toggle. Without the attribute the tooltip is click-activated.
data-statusSet on the inner .nds-feedback wrappers (inside the trigger and inside the balloon) to drive icon glyph and color. Values: help, info, success, warning, error, neutral. Do not put data-status on .nds-tooltip itself.
data-stateManaged by JS on .nds-tooltip. Set to open while the balloon is visible; absent when closed. Used internally for CSS styling hooks (e.g. the idle-trigger neutral background).
hiddenSet on .nds-tooltip-balloon in source so the balloon starts closed. JS flips the attribute on open and close.
aria-labelSet on .nds-tooltip-trigger to describe the tooltip purpose for screen readers (e.g. "What is this?").
data-position-verticalStamped by JS on both .nds-tooltip and .nds-tooltip-balloon when the balloon flips above the trigger due to insufficient space below. Value: top. Drives the CSS arrow-flip rule. Read-only; do not set manually.

CSS Custom Properties

PropertyDefaultDescription
--tooltip-max-width240pxClamp on balloon width. Text wraps automatically beyond this.
--tooltip-paddingvar(--spacing-md)Inner padding around the balloon content.
--tooltip-gapvar(--spacing-md)Gap between the icon chip and the body, and between title and message.
--tooltip-arrow-size10pxSide length of the rotated arrow square.
--tooltip-background-defaultvar(--colors-base-white)Balloon surface color in light theme.
--tooltip-background-inversevar(--colors-neutral-800)Balloon surface color in dark theme.
--tooltip-text-heading-defaultvar(--text-display)Title color in light theme.
--tooltip-text-paragraph-defaultvar(--text-primary-paragraph)Message body color in light theme.
--tooltip-text-heading-inversevar(--colors-neutral-50)Title color in dark theme.
--tooltip-text-paragraph-inversevar(--colors-neutral-100)Message body color in dark theme.

JavaScript API

The NDS.Tooltip namespace exposes initialization and factory methods. Every initialized tooltip also attaches an instance to its root element at el.ndsTooltip, with open(), close(), and destroy() methods. Tooltips dispatch nds:tooltip:opened and nds:tooltip:closed events that bubble to the document.

// ── Initialize ─────────────────────────────────────── // Called automatically on DOMContentLoaded for any // `.nds-tooltip` already in the DOM. Call again after // injecting new tooltips dynamically. NDS.Tooltip.init(); NDS.Tooltip.reinit(); // Alias — same as init() // ── Create a single instance ───────────────────────── // Useful when you know the element and want the handle // back immediately. const el = document.querySelector('.nds-tooltip'); const tooltip = NDS.Tooltip.create(el); // ── Instance access from the DOM ───────────────────── // Every initialized root has `ndsTooltip` attached. el.ndsTooltip.open(); el.ndsTooltip.close(); // Closes if open, detaches all listeners, and removes // the init sentinel so the element can be re-initialized. el.ndsTooltip.destroy(); // ── Listen for open/close events ───────────────────── // Events bubble, so you can delegate from a parent. el.addEventListener('nds:tooltip:opened', (e) => { const { tooltip, trigger, balloon, isOpen } = e.detail; // ... react to the tooltip opening }); el.addEventListener('nds:tooltip:closed', (e) => { // ... react to the tooltip closing });
Last Modified Date: 17/07/2026 - 06:24 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks