🧩 Inline SVG and Manipulation
When you write SVG directly inside your HTML, every shape becomes a real DOM node — something JavaScript can create, style, animate, and wire to events. This lesson turns static vector art into living, data-driven graphics: you'll manipulate elements with the DOM API, animate with CSS, generate charts from data, and keep it all accessible.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain why inline SVG beats
<img>for interactive graphics - Read and modify SVG elements with
getAttribute/setAttributeand create them withcreateElementNS - Style and animate SVG with CSS transitions, keyframes, and the line-drawing trick
- Generate SVG from data to build a bar chart programmatically
- Add interactivity (hover, click, tooltips) and proper accessibility
Estimated Time: 40–50 minutes • Difficulty: Intermediate
Hands-on: Build a data-driven bar chart that reacts to hover and updates live.
In This Lesson
Why Inline SVG?
Inline SVG means writing the <svg> markup directly in your HTML document instead of pointing an <img> at an external .svg file. The payoff is enormous: the shapes join the page's DOM, so they behave like any other element — selectable, styleable, scriptable.
💡 A useful analogy: An <img src="chart.svg"> is a sealed photograph of a chart — you can hang it on the wall but not touch what's inside. Inline SVG is the chart itself, with every bar, label, and axis handed to you as a component you can paint, move, and click.
Because each element is a DOM node, inline SVG unlocks:
- Direct manipulation of individual shapes via JavaScript
- CSS styling, including hover states and media queries
- Interactive behaviors — clicks, drags, tooltips
- Dynamic updates driven by data or application state
Embedding Methods Compared
There are several ways to put SVG on a page. They trade convenience for control — and only inline SVG gives you full JavaScript access.
| Method | DOM access | CSS control | JS control | Best for |
|---|---|---|---|---|
Inline <svg>…</svg> | Full | Full | Full | Interactive graphics |
<img src="x.svg"> | None | Filters only | None | Static images |
background: url(x.svg) | None | Filters only | None | Decoration |
<object data="x.svg"> | Limited | Internal only | Limited | Isolated widgets |
<!-- Inline: every child is reachable from JS and CSS -->
<svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle id="dot" cx="50" cy="50" r="40" fill="#3b82f6" stroke="#0d47a1" stroke-width="2" />
<text x="50" y="54" text-anchor="middle" fill="#fff" font-size="10">Inline</text>
</svg>
💡 Rule of thumb
If the graphic needs to react — to hover, clicks, data, or state — inline it. If it's purely decorative or a fixed picture, an <img> caches better and keeps your HTML lean.
Manipulating with JavaScript
Reading and changing existing shapes uses the same DOM methods you already know. The one twist: SVG lives in its own XML namespace, so creating new elements requires createElementNS, not createElement.
// Read & change an existing element
const dot = document.getElementById('dot');
dot.setAttribute('fill', 'tomato');
dot.setAttribute('r', '30');
dot.addEventListener('click', () => dot.setAttribute('fill', 'seagreen'));
// Create a NEW element — must use the SVG namespace
const SVG_NS = 'http://www.w3.org/2000/svg';
const rect = document.createElementNS(SVG_NS, 'rect');
rect.setAttribute('x', '10');
rect.setAttribute('y', '10');
rect.setAttribute('width', '80');
rect.setAttribute('height', '80');
rect.setAttribute('fill', '#f59e0b');
const svg = document.querySelector('svg');
svg.appendChild(rect); // it appears immediately
⚠️ createElement silently fails for SVG
If you use document.createElement('rect'), the browser creates an HTML element named "rect" that renders as nothing. It won't throw an error — it just won't show up. Always use createElementNS with the http://www.w3.org/2000/svg namespace for SVG.
Styling & Animating with CSS
Inline SVG elements respond to CSS just like HTML — element, class, ID, and pseudo-class selectors all work. The SVG-specific properties (fill, stroke, stroke-width, stroke-dasharray) sit alongside standard ones like opacity and transform.
.dot {
fill: #3b82f6;
transition: fill .3s ease, transform .3s ease;
transform-box: fill-box; /* so transforms pivot on the shape */
transform-origin: center;
}
.dot:hover { fill: #ef4444; transform: scale(1.2); }
/* Pulsing keyframe animation */
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
}
.pulse { animation: pulse 2s infinite ease-in-out; }
The line-drawing trick
A favorite SVG effect animates a path as if it's being drawn by hand. It works by setting stroke-dasharray to the path's total length (one giant dash), then animating stroke-dashoffset from that length down to zero:
.draw {
fill: none;
stroke: #6366f1;
stroke-width: 2;
stroke-dasharray: 1000; /* >= the path length */
stroke-dashoffset: 1000; /* start fully "hidden" */
animation: draw 3s forwards ease-in-out;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
✅ Prefer CSS transforms over attribute tweaks for motion
Animating transform in CSS is GPU-friendly and doesn't trigger layout. Repeatedly changing geometry attributes (x, cx, width) forces the browser to recompute the shape each frame. Use CSS for movement; save attribute changes for structural updates.
Generating SVG from Data
The real power appears when SVG is built from data. Loop over an array, create one element per data point, and you have a chart — with each bar a live object you can style and wire to events. Here's a compact, modern bar chart generator:
const SVG_NS = 'http://www.w3.org/2000/svg';
const data = [
{ label: 'Jan', value: 120 },
{ label: 'Feb', value: 150 },
{ label: 'Mar', value: 180 },
{ label: 'Apr', value: 110 },
{ label: 'May', value: 200 },
];
function renderChart(svg, data) {
const W = 500, H = 300, pad = 40;
const max = Math.max(...data.map(d => d.value));
const barW = (W - pad * 2) / data.length * 0.7;
const gap = (W - pad * 2) / data.length;
svg.replaceChildren(); // clear any previous render
data.forEach((d, i) => {
const h = (d.value / max) * (H - pad * 2);
const x = pad + i * gap;
const y = H - pad - h;
const bar = document.createElementNS(SVG_NS, 'rect');
bar.setAttribute('x', x);
bar.setAttribute('y', y);
bar.setAttribute('width', barW);
bar.setAttribute('height', h);
bar.setAttribute('fill', '#3b82f6');
bar.dataset.value = d.value;
bar.addEventListener('mouseenter', () => bar.setAttribute('fill', '#f59e0b'));
bar.addEventListener('mouseleave', () => bar.setAttribute('fill', '#3b82f6'));
const label = document.createElementNS(SVG_NS, 'text');
label.textContent = d.label;
label.setAttribute('x', x + barW / 2);
label.setAttribute('y', H - pad + 16);
label.setAttribute('text-anchor', 'middle');
label.setAttribute('font-size', '12');
svg.append(bar, label);
});
}
renderChart(document.getElementById('chart'), data);
Techniques on display
- One DOM element created per data point with
createElementNS - Values scaled to the chart with a computed
max - Data stashed on each bar via
datasetfor later lookups replaceChildren()to cleanly re-render when data changes
Interactivity Patterns
Because shapes are DOM nodes, they take event listeners directly. A few patterns cover most needs.
Hover feedback (CSS is usually enough)
.node { fill: #3b82f6; transition: fill .2s; }
.node:hover { fill: #ef4444; }
Click to toggle selection
shape.addEventListener('click', (event) => {
const selected = shape.classList.toggle('selected');
shape.setAttribute('stroke-width', selected ? 3 : 1);
event.stopPropagation();
});
Tooltip that follows the cursor
const tip = document.getElementById('tooltip');
bar.addEventListener('mousemove', (event) => {
tip.textContent = `Value: ${bar.dataset.value}`;
tip.style.left = `${event.pageX + 12}px`;
tip.style.top = `${event.pageY - 24}px`;
tip.style.display = 'block';
});
bar.addEventListener('mouseleave', () => { tip.style.display = 'none'; });
Accessibility
Interactive SVG must work for keyboard and screen-reader users too. The essentials:
- Give the SVG
role="img"with a<title>and<desc>, referenced byaria-labelledby - Make clickable shapes focusable (
tabindex="0"), give themrole="button"and anaria-label - Support the keyboard — activate on Enter/Space, not just mouse clicks
- Don't rely on color alone; keep contrast sufficient
<svg role="img" aria-labelledby="t d">
<title id="t">Quarterly sales, 2025</title>
<desc id="d">Bar chart; Q3 shows the strongest growth.</desc>
<g role="button" tabindex="0" aria-label="Q1 sales: 250,000">
<rect class="bar" x="50" y="150" width="50" height="100" />
<text x="75" y="265" text-anchor="middle">Q1</text>
</g>
</svg>
const q1 = document.querySelector('[aria-label^="Q1"]');
q1.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); showDetails('q1'); }
});
q1.addEventListener('click', () => showDetails('q1'));
Hands-on Exercise
🏋️ A live, hoverable bar chart
Objective: Generate an SVG chart from data, add hover feedback, and update it on a button click.
Instructions:
- Start with an empty
<svg id="chart" viewBox="0 0 500 300">and a data array. - Write a
renderChart()that creates one<rect>per data point withcreateElementNS. - On hover, change each bar's fill; on
mouseleave, restore it. - Add a "Randomize" button that replaces the values and re-renders with
replaceChildren().
💡 Hint
Scale bar heights relative to the data's maximum so the tallest bar always fills the chart: height = (value / max) * chartHeight. Re-running renderChart() with new data is all "updating" requires.
✅ Sample solution
const SVG_NS = 'http://www.w3.org/2000/svg';
const svg = document.getElementById('chart');
let data = [40, 90, 60, 120, 80];
function renderChart(values) {
const H = 300, pad = 30, max = Math.max(...values);
const gap = (500 - pad * 2) / values.length;
svg.replaceChildren();
values.forEach((v, i) => {
const h = (v / max) * (H - pad * 2);
const bar = document.createElementNS(SVG_NS, 'rect');
bar.setAttribute('x', pad + i * gap);
bar.setAttribute('y', H - pad - h);
bar.setAttribute('width', gap * 0.7);
bar.setAttribute('height', h);
bar.setAttribute('fill', '#3b82f6');
bar.addEventListener('mouseenter', () => bar.setAttribute('fill', '#f59e0b'));
bar.addEventListener('mouseleave', () => bar.setAttribute('fill', '#3b82f6'));
svg.appendChild(bar);
});
}
document.getElementById('shuffle').addEventListener('click', () => {
data = data.map(() => Math.floor(Math.random() * 120) + 20);
renderChart(data);
});
renderChart(data);
🎯 Quick Quiz
Question 1: Why must you use createElementNS instead of createElement to build SVG shapes in JavaScript?
Question 2: Which property pair is used to animate a path so it appears to be drawn by hand?
Question 3: You want a data chart where each bar has its own click handler and a screen-reader-friendly label. What's the best approach?
Best Practices
✅ Do
- Inline SVG whenever the graphic needs to react to data, hover, or clicks
- Use
createElementNSwith the SVG namespace for new elements - Prefer CSS
transform/transitions for motion over per-frame attribute changes - Batch DOM insertions and use
replaceChildren()for clean re-renders - Add
role,<title>,<desc>,tabindex, and keyboard handlers
⚠️ Don't
- Reach for
createElement(no namespace) — the shape silently won't render - Inline enormous, repeated SVGs — use
<symbol>/<use>or cache as<img> - Animate geometry attributes every frame when a CSS transform would do
- Convey meaning by color alone or ship interactive SVG with no accessible names
Summary & Quiz
🎉 Key Takeaways
- Inline SVG puts shapes in the DOM, so they're styleable, scriptable, and clickable.
- Modify with
setAttribute; create withcreateElementNSand the SVG namespace. - Use CSS for hover, transitions, keyframes, and the dash-offset line-drawing effect.
- Generate graphics from data by looping and appending one element per data point.
- Interactive SVG still needs ARIA, focus, and keyboard support to be accessible.
📚 Further Reading
- MDN — SVG reference
- CSS-Tricks — A Complete Guide to SVG
- D3.js — data-driven SVG at scale
🚀 What's Next?
You've now covered forms and all of Module 4's interactive HTML APIs. Next you'll put them to work in the Weekend Project: HTML Forms & HTML5 APIs, combining validation, drag-and-drop, Canvas, and SVG into one small application.
🎉 Nice work!
Your SVGs are now living, data-driven, and accessible. Time to build something with everything you've learned.