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.
| Method | Styled | Appears at once | Keyboard | Needs script |
|---|---|---|---|---|
<title> inside the shape | No | No, after a pause | Not reliably | No |
| Label inside the SVG, shown by CSS | Yes | Yes | With tabindex | No |
| HTML box placed by JavaScript | Yes | Yes | Yes | Yes |
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, andevent.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, withrole="img", so a screen reader announces it when the shape is focused. The visible box only repeats it, which is why the box is markedaria-hidden="true". getBoundingClientRect()gives the shape’s position on the screen in pixels, whatever the viewBox, scaling or transforms. Withposition: fixedon the tooltip, those numbers can be used as they are.pointer-events: noneon the tooltip stops it from catching the pointer itself, which would make it flicker when it overlaps the shape.clickshows 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:hovernor 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
titleattribute 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 see | Cause | Fix |
|---|---|---|
No tooltip from <title> | The SVG is in an <img>, or title was written as an attribute | Inline the SVG and use the element |
The <title> tooltip is slow to appear | That is the browser’s own delay | Use method 2 or 3 |
| The tooltip flickers | The tooltip sits under the pointer and takes the hover | pointer-events: none on the tooltip |
| The tooltip is in the wrong place when the page is scrolled | position: absolute with numbers from getBoundingClientRect() | Use position: fixed, or add scrollX and scrollY |
| Hovering the gap inside a shape shows nothing | The shape has fill="none", so only its stroke responds | pointer-events="all" on the shape, or a transparent fill |
| A CSS label is cut off | It lies outside the viewBox | Use an HTML tooltip, or add overflow="visible" to the <svg> |
| Nothing happens on a phone | Touch screens have no hover | Show the tooltip on click as well |
| The keyboard cannot reach a shape | It is not focusable | tabindex="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.