Click
addEventListener
Toggle state, open UI, or update attributes on click.

SVG is not only for static drawings — shapes can respond to clicks, hover, touch, and keyboard input. This tutorial covers event listeners, CSS :hover / :focus-visible, pointer-events, accessible button patterns, five worked examples, and how interactivity compares to SVG links.
addEventListener
Toggle state, open UI, or update attributes on click.
CSS feedback
Highlight shapes with fill, opacity, and transforms.
tabindex
Make controls focusable and activate with Enter/Space.
aria-label
Describe interactive controls for assistive tech.
pointer-events
Control which layers receive mouse and touch hits.
Split roles
Use CSS for motion; use JS for state and logic.
Interactive SVG powers dashboards, maps, icon buttons, and micro-interactions. Because SVG nodes live in the DOM, you can style them with CSS and wire them with the same JavaScript events you use on buttons and links.
The best pattern is simple: CSS for hover/focus polish, JavaScript for state changes, and accessibility attributes so keyboard and screen-reader users get the same experience.
Static icons miss opportunities. Interactive SVG keeps visuals crisp at any size while behaving like real UI controls — clickable, focusable, and themeable.
Attach click, keydown, mouseover, and more.
:hover and :focus-visible keep feedback smooth.
tabindex, role, and aria-label unlock keyboard use.
pointer-events decides what can be clicked.
In short: select a shape, style its hover/focus states, listen for click and keyboard keys, and label it for accessibility.
Core pattern for an interactive SVG control:
<svg viewBox="0 0 200 120">
<rect id="btn" x="40" y="30" width="120" height="60" rx="12"
fill="#2563eb" tabindex="0" role="button"
aria-label="Interactive rectangle" />
</svg>
<script>
var el = document.getElementById("btn");
el.addEventListener("click", function () {
// handle interaction
});
</script> | Piece | Role | Description |
|---|---|---|
click | Pointer / touch | Primary activation for mice and fingers. |
:hover | CSS feedback | Visual polish while the pointer is over the shape. |
tabindex="0" | Focus | Adds the element to the keyboard tab order. |
role="button" | Semantics | Announces the control as a button to assistive tech. |
aria-label | Name | Accessible name when there is no visible text. |
pointer-events | Hit testing | Enable, disable, or pass through pointer hits. |
<style>
#toggleRect { cursor: pointer; transition: fill 160ms ease; }
#toggleRect:hover { opacity: 0.92; }
#toggleRect:focus-visible { outline: 3px solid #0f172a; outline-offset: 2px; }
</style>
<svg width="240" height="160" viewBox="0 0 240 160">
<rect id="toggleRect" x="55" y="35" width="130" height="90" rx="16"
fill="#2563eb" tabindex="0" role="button"
aria-label="Toggle rectangle color" />
</svg>
<script>
var rect = document.getElementById("toggleRect");
function toggle() {
var isBlue = rect.getAttribute("fill") === "#2563eb";
rect.setAttribute("fill", isBlue ? "#ef4444" : "#2563eb");
}
rect.addEventListener("click", toggle);
rect.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
});
</script> | Goal | Code |
|---|---|
| Listen for click | el.addEventListener("click", handler) |
| Hover style | #shape:hover { fill: #ef4444; } |
| Make focusable | tabindex="0" |
| Activate with keyboard | Handle Enter and Space on keydown |
| Ignore hits on text | <text pointer-events="none"> |
| Pass clicks through | pointer-events="none" on overlays |
Pick the lightest approach that matches the job.
custom UIToggles, tooltips, dashboards, and custom controls
visual feedbackHover/focus polish without changing application state
pixel hit testsHarder a11y — prefer SVG when shapes are UI
Reach for interactive SVG when the graphic is the interface.
Toggleable glyphs with hover, focus, and click handlers.
Highlight regions, show tooltips, and select series.
Press states, soft lifts, and colour toggles on shapes.
Prefer <a> links when the only goal is going to a URL.
Touch devices lack hover — always provide a click/focus path.
Key benefit: crisp vector UI with the same event and accessibility model as HTML.
Five starter snippets for interactive SVG. Click View Output for a live demo, or Try It Yourself to edit the full page.
Toggle on click, then add hover feedback with a tooltip.
Click (or press Enter/Space) to toggle the rectangle colour. Includes keyboard support and a focus ring.
<style>
#toggleRect{cursor:pointer;transition:fill 160ms ease,transform 160ms ease}
#toggleRect:hover{transform:translateY(-1px)}
#toggleRect:focus-visible{outline:none}
#toggleRect.is-focus{stroke:#0f172a;stroke-width:3}
</style>
<svg width="240" height="160" viewBox="0 0 240 160">
<rect id="toggleRect" x="55" y="35" width="130" height="90" rx="16" fill="#2563eb"
tabindex="0" role="button" aria-label="Toggle rectangle color" />
</svg>
<script>
var rect = document.getElementById("toggleRect");
function toggle() {
var isBlue = rect.getAttribute("fill") === "#2563eb";
rect.setAttribute("fill", isBlue ? "#ef4444" : "#2563eb");
}
rect.addEventListener("click", toggle);
rect.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
});
rect.addEventListener("focus", function () { rect.classList.add("is-focus"); });
rect.addEventListener("blur", function () { rect.classList.remove("is-focus"); });
</script> The rectangle is a focusable button. Click and keyboard handlers call the same toggle() function so pointer and keyboard users stay in sync.
Use CSS for hover motion and JavaScript only to show contextual tip text.
<svg width="320" height="160" viewBox="0 0 320 160">
<style>
.chip{cursor:pointer;transition:transform 140ms ease,opacity 140ms ease}
.chip:hover{transform:translateY(-2px);opacity:0.95}
.chip.is-focus rect{stroke:#0f172a;stroke-width:3}
</style>
<g id="chipA" class="chip" tabindex="0" role="button" aria-label="Blue chip">
<rect x="32" y="44" width="120" height="72" rx="18" fill="#3b82f6" />
<text x="92" y="86" text-anchor="middle" font-size="14" fill="white"
font-family="system-ui,Segoe UI,Arial" pointer-events="none">Blue</text>
</g>
<g id="chipB" class="chip" tabindex="0" role="button" aria-label="Green chip">
<rect x="168" y="44" width="120" height="72" rx="18" fill="#10b981" />
<text x="228" y="86" text-anchor="middle" font-size="14" fill="white"
font-family="system-ui,Segoe UI,Arial" pointer-events="none">Green</text>
</g>
</svg>
<div id="tip">Hover or focus a chip</div>
<script>
var tip = document.getElementById("tip");
function bindChip(id, msg) {
var el = document.getElementById(id);
function show() { if (tip) tip.textContent = msg; }
el.addEventListener("mouseover", show);
el.addEventListener("focus", function () { el.classList.add("is-focus"); show(); });
el.addEventListener("blur", function () { el.classList.remove("is-focus"); });
}
bindChip("chipA", "Blue chip selected (demo)");
bindChip("chipB", "Green chip selected (demo)");
</script> CSS handles the lift-on-hover animation. JS only updates the tip string — a clean split that stays fast on touch and desktop.
Keyboard focus, hit testing, and grouped targets.
A circle control that announces itself as a button and reacts to Tab + Enter/Space.
<style>
#pulseBtn { cursor: pointer; transition: fill 160ms ease, stroke-width 160ms ease; }
#pulseBtn:focus-visible { stroke: #0f172a; stroke-width: 4; }
</style>
<svg width="200" height="160" viewBox="0 0 200 160">
<circle id="pulseBtn" cx="100" cy="80" r="42" fill="#8b5cf6"
tabindex="0" role="button" aria-pressed="false"
aria-label="Pulse button" />
</svg>
<p id="status">Pressed: false</p>
<script>
var btn = document.getElementById("pulseBtn");
var status = document.getElementById("status");
function activate() {
var pressed = btn.getAttribute("aria-pressed") === "true";
btn.setAttribute("aria-pressed", String(!pressed));
btn.setAttribute("fill", pressed ? "#8b5cf6" : "#22c55e");
status.textContent = "Pressed: " + (!pressed);
}
btn.addEventListener("click", activate);
btn.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
activate();
}
});
</script> aria-pressed tracks toggle state for assistive tech. :focus-visible (or a focus stroke) shows where keyboard focus is.
pointer-events Pass-ThroughDisable hits on an overlay so clicks reach the shape underneath.
<svg width="240" height="160" viewBox="0 0 240 160">
<rect id="target" x="50" y="40" width="140" height="80" rx="12"
fill="#2563eb" style="cursor:pointer" />
<!-- Decorative overlay that should NOT steal clicks -->
<rect x="50" y="40" width="140" height="80" rx="12"
fill="white" fill-opacity="0.15" pointer-events="none" />
<text x="120" y="88" text-anchor="middle" fill="white" font-size="14"
font-family="system-ui,Segoe UI,Arial" pointer-events="none">Click me</text>
</svg>
<p id="hits">Clicks: 0</p>
<script>
var n = 0;
var target = document.getElementById("target");
var hits = document.getElementById("hits");
target.addEventListener("click", function () {
n += 1;
hits.textContent = "Clicks: " + n;
});
</script> Without pointer-events="none", the overlay or text could intercept clicks. Disabling hit testing on decorations keeps the real target reliable.
Wrap a shape and label in a <g> so the whole card acts as one button.
<style>
.card { cursor: pointer; transition: transform 140ms ease; }
.card:hover { transform: translateY(-2px); }
.card.is-on rect { fill: #f59e0b; }
</style>
<svg width="260" height="150" viewBox="0 0 260 150">
<g id="card" class="card" tabindex="0" role="button" aria-pressed="false"
aria-label="Select card">
<rect x="40" y="30" width="180" height="90" rx="16" fill="#0ea5e9" />
<text x="130" y="84" text-anchor="middle" fill="white" font-size="16"
font-family="system-ui,Segoe UI,Arial" pointer-events="none">Select</text>
</g>
</svg>
<script>
var card = document.getElementById("card");
function toggle() {
var on = card.getAttribute("aria-pressed") === "true";
card.setAttribute("aria-pressed", String(!on));
card.classList.toggle("is-on", !on);
}
card.addEventListener("click", toggle);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
});
</script> Events bubble to the group, so one listener covers the whole card. Text uses pointer-events="none" so it never steals the click.
Where interactive SVG shows up in real products.
Toggleable toolbar glyphs with hover and focus states.
Example: like / bookmark icon.
Highlight series, show values, and select points.
Example: bar hover tooltip.
Clickable regions with keyboard-friendly labels.
Example: country select map.
Hotspots that reveal details on click or focus.
Example: annotated device outline.
Press feedback and soft lifts without leaving SVG.
Example: card select pulse.
Teach DOM events using visible vector controls.
Example: classroom click labs.
Pro Tip: if the only action is navigation, use SVG links — save JS for state changes and custom behaviour.
Why interactive SVG beats image maps and canvas for many UIs.
Vector hit targets scale cleanly on any screen.
Use the same listeners as HTML buttons and links.
Focus, roles, and names work with assistive tech.
Hover and focus styles without rewriting geometry.
Target exact shapes — not rectangular image hotspots.
Pro Tip: keep motion in CSS and logic in JS — your interactions stay smoother and easier to maintain.
Follow these practices for reliable interactive SVG.
Animate fill, opacity, and transform with CSS whenever possible.
Use tabindex="0" and handle Enter/Space for button-like controls.
Provide aria-label or visible text so the action has a name.
Set pointer-events="none" on <text> inside clickable groups.
Use a transparent wider stroke or invisible fill so targets are easy to tap.
Pro Tip: never ship hover-only behaviour — always mirror the action on click/focus for touch and keyboard users.
Avoid these mistakes when clicks or focus seem broken.
A transparent layer on top can block the real target.
→ Set pointer-events="none" on decorative overlays.
fill="none" with no stroke may not receive clicks.
→ Add a stroke, fill, or invisible hit shape.
Querying the SVG before it exists returns null.
→ Run scripts after the markup, or wait for DOMContentLoaded.
Click-only controls exclude keyboard users.
→ Add tabindex="0" and Enter/Space handlers.
Touch devices never get a lasting hover state.
→ Mirror hover feedback with click/focus behaviour.
Pro Tip: if clicks fail, inspect overlays and pointer-events first — those two cause most “dead SVG button” bugs.
Give the shape or group an id (or class) so JavaScript can select it after the SVG is in the DOM.
Use CSS :hover and :focus-visible for fast visual feedback without waiting on JS.
Listen for click and keydown. Share one handler so pointer and keyboard stay consistent.
Add tabindex, role, and aria-label (or a <title>) so the control has a name and keyboard path.
Your SVG behaves like a button: clickable, focusable, and clear to assistive technologies.
tabindex, a name, and keyboard activation.pointer-events controls hit testing for overlays and text.Quick Takeaway: CSS for feel, JS for state, and accessibility attributes so every user can activate the control.
SVG DOM events, CSS :hover / :focus-visible, tabindex, and pointer-events are supported in all modern browsers. Test keyboard focus rings and touch targets on real devices.
Inline SVG participates in the same event model as HTML. Use progressive enhancement and keep hit areas large enough for touch.
Bottom line: Safe for production UI. Prefer inline SVG (not image tags) when you need events, and verify focus styles with a keyboard.
Interactive SVG turns vector art into real controls — clickable, hoverable, and keyboard-friendly — using the same CSS and JavaScript skills you already know.
Practice the five examples above, then continue to SVG Links when navigation is the primary goal.
Label controls, support Enter/Space, and never ship hover-only interactions.
tabindex="0", role, and an accessible name for button-like shapesEnter and Space the same way as clickpointer-events="none" on labels and decorative overlays<a> would dofill="none" shapes to be easy to click without a strokeTurn shapes into real UI controls.
click & keydown
API:hover polish
Styletabindex + keys
A11ypointer-events
TargetUse <a> for URLs
NavSVG shapes are DOM elements — you can attach the same click, keydown, and CSS :hover / :focus-visible patterns you already use on HTML. Putting interactive markup on a group (<g>) lets one listener cover a whole card — shape, icon, and label — as a single control.
Learn how <a> wraps shapes for native navigation without custom click handlers.
5 people found this page helpful