SVG tooltip: title element, CSS or JavaScript

Three ways to add a tooltip to an SVG shape: the built-in title element, a CSS hover label inside the drawing, or an HTML box placed by JavaScript, with code.

Published

The quickest tooltip in SVG is a <title> element placed inside a shape: the browser shows its text when the pointer rests there. For a tooltip you can style, that appears at once, and that works from the keyboard, use an HTML element placed over the SVG with a few lines of JavaScript. Both need the SVG to be inline in the page.

MethodStyledAppears at onceKeyboardNeeds script
<title> inside the shapeNoNo, after a pauseNot reliablyNo
Label inside the SVG, shown by CSSYesYesWith tabindexNo
HTML box placed by JavaScriptYesYesYesYes

1. The title element

<svg viewBox="0 0 160 80" width="320">
  <rect x="10" y="20" width="40" height="50" fill="gold">
    <title>Gold: 40 units</title>
  </rect>
  <rect x="60" y="10" width="40" height="60" fill="teal">
    <title>Teal: 60 units</title>
  </rect>
</svg>

Each bar has its own tooltip. <title> is a child element, not an attribute: <rect title="..."> does nothing, because SVG has no title attribute. Make it the first child of the shape or group it describes. On a <g>, it covers everything in the group.

The limits are the browser’s. The tooltip appears after a pause, in the system’s own style, and cannot be changed with CSS. The SVG specification describes it as shown on hover, so it cannot be counted on with a touch screen or a keyboard. Use it for hints that nobody has to see. SVG title and alt text covers what <title> does for screen readers.

2. A label inside the SVG, shown by CSS

Draw the tooltip as part of the picture, hide it, and show it when its group is hovered or focused:

<svg viewBox="0 0 160 80" width="320">
  <style>
    .tip { visibility: hidden; pointer-events: none; }
    .bar:hover .tip, .bar:focus .tip { visibility: visible; }
  </style>
  <g class="bar" tabindex="0">
    <rect x="60" y="30" width="40" height="40" fill="teal"/>
    <g class="tip">
      <rect x="45" y="6" width="70" height="18" rx="3" fill="#222"/>
      <text x="80" y="19" text-anchor="middle" font-size="10" fill="#fff">Teal: 60 units</text>
    </g>
  </g>
</svg>

Hover the bar, or reach it with the Tab key, and the dark label appears above it. tabindex="0" is what lets the keyboard reach the group.

This needs no script and travels with the SVG, but it has three costs:

  • The label is in the drawing’s coordinates. It scales with the SVG, and anything that falls outside the viewBox is cut off, so a tooltip near the edge loses a corner.
  • SVG text does not wrap or size its background, so every label needs its box drawn to fit by hand.
  • A label is covered by any shape written after it in the code. See SVG z-index.

3. An HTML tooltip placed by JavaScript

This is the method to use for charts and maps. The tooltip is one HTML <div> outside the SVG. A script fills it with the text for the shape under the pointer and moves it into place. This is a complete example:

<div id="chart">
  <svg viewBox="0 0 160 80" width="320">
    <rect role="img" aria-label="Gold: 40 units" tabindex="0" x="10" y="20" width="40" height="50" fill="gold"/>
    <rect role="img" aria-label="Teal: 60 units" tabindex="0" x="60" y="10" width="40" height="60" fill="teal"/>
    <rect role="img" aria-label="Red: 30 units" tabindex="0" x="110" y="40" width="40" height="30" fill="crimson"/>
  </svg>
</div>
<div id="tip" aria-hidden="true" hidden></div>

<style>
  #tip {
    position: fixed;
    padding: 4px 8px;
    border-radius: 4px;
    background: #222;
    color: #fff;
    font: 14px sans-serif;
    pointer-events: none;
  }
</style>

<script>
  const chart = document.querySelector('#chart');
  const tip = document.querySelector('#tip');

  function show(shape) {
    tip.textContent = shape.getAttribute('aria-label');
    tip.hidden = false;
    const box = shape.getBoundingClientRect();
    tip.style.left = box.left + box.width / 2 - tip.offsetWidth / 2 + 'px';
    tip.style.top = box.top - tip.offsetHeight - 6 + 'px';
  }

  function hide() {
    tip.hidden = true;
  }

  function showFor(event) {
    const shape = event.target.closest('[aria-label]');
    if (shape) show(shape);
    else hide();
  }

  chart.addEventListener('pointerover', showFor);
  chart.addEventListener('pointerout', hide);
  chart.addEventListener('focusin', showFor);
  chart.addEventListener('focusout', hide);
  chart.addEventListener('click', showFor);
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape') hide();
  });
</script>

Hover a bar and a dark box with its text appears centred above it. Tab moves from bar to bar and shows each tooltip in turn. Escape hides it.

How it works:

  • One listener for every shape. The events are caught on the <div> around the SVG, and event.target.closest('[aria-label]') finds the shape they came from. A map with three hundred regions needs no more code than a chart with three bars.
  • The listeners are on a <div>, not on the <svg>. In Chrome an SVG element with a focus listener becomes a stop for the Tab key itself, which would add an empty stop before the first bar.
  • The text lives on the shape. Each shape carries its text in aria-label, with role="img", so a screen reader announces it when the shape is focused. The visible box only repeats it, which is why the box is marked aria-hidden="true".
  • getBoundingClientRect() gives the shape’s position on the screen in pixels, whatever the viewBox, scaling or transforms. With position: fixed on the tooltip, those numbers can be used as they are.
  • pointer-events: none on the tooltip stops it from catching the pointer itself, which would make it flicker when it overlaps the shape.
  • click shows the tooltip after a tap, for touch screens, where nothing hovers.

For a path, a circle or a group the code is the same: put the aria-label on whatever should respond.

Following the pointer

On a large shape, such as a country on a map, a tooltip in the middle can be far from the pointer. Position it from the event instead:

chart.addEventListener('pointermove', (event) => {
  tip.style.left = event.clientX + 12 + 'px';
  tip.style.top = event.clientY + 12 + 'px';
});

clientX and clientY are in the same screen pixels as position: fixed. Keep the pointerover listener to set the text, and drop the two positioning lines from show.

Keeping it on the screen

A tooltip above the top row of a chart, or beside a shape at the edge of the window, can run off the screen. Clamp the position:

const left = box.left + box.width / 2 - tip.offsetWidth / 2;
tip.style.left = Math.max(4, Math.min(left, innerWidth - tip.offsetWidth - 4)) + 'px';

and place the tooltip below the shape when box.top is too small for it to fit above.

What will not work

  • An SVG in an <img> or a CSS background. The page cannot see the shapes inside, and the file receives no pointer events, so neither <title> nor :hover nor a script can respond. One tooltip for the whole picture is possible with the HTML attribute: <img src="chart.svg" title="Sales by month" alt="...">. For tooltips on parts, inline the SVG: see SVG in HTML.
  • A title attribute on a shape. Use the <title> element.
  • HTML written straight inside the SVG. A <div> between <svg> and </svg> ends the drawing early: the browser moves the <div>, and every shape after it, out of the SVG. HTML can be placed in the drawing with foreignObject, but a tooltip there is scaled and clipped like the label in method 2. Outside the SVG is simpler.

Common problems

What you seeCauseFix
No tooltip from <title>The SVG is in an <img>, or title was written as an attributeInline the SVG and use the element
The <title> tooltip is slow to appearThat is the browser’s own delayUse method 2 or 3
The tooltip flickersThe tooltip sits under the pointer and takes the hoverpointer-events: none on the tooltip
The tooltip is in the wrong place when the page is scrolledposition: absolute with numbers from getBoundingClientRect()Use position: fixed, or add scrollX and scrollY
Hovering the gap inside a shape shows nothingThe shape has fill="none", so only its stroke respondspointer-events="all" on the shape, or a transparent fill
A CSS label is cut offIt lies outside the viewBoxUse an HTML tooltip, or add overflow="visible" to the <svg>
Nothing happens on a phoneTouch screens have no hoverShow the tooltip on click as well
The keyboard cannot reach a shapeIt is not focusabletabindex="0"

Questions

Can a tooltip hold more than plain text? With the HTML method, yes: set innerHTML in place of textContent to show a heading, a value and a small table. Only do that with text you wrote yourself, never with text from users. Keep links and buttons out of it, since a tooltip cannot be reached with the keyboard.

Does this work in React or Vue? The same parts apply: handlers for pointer and focus events on the shapes, the hovered item kept in state, and one tooltip element positioned from getBoundingClientRect().

How do I find which shape is which in a file I did not draw? Open it in the SVG viewer and read the code. Illustrator can name each shape’s id after its layer when it exports: see exporting SVG from Illustrator.