Bringing It to Life: Add JavaScript to Your Hand-Coded CV
Part 3 of 6 in the series “Hand-Code Your CV: From Empty Folder to Live on Azure”
So far we have HTML for structure (Part 1) and CSS for appearance (Part 2). The third ingredient is JavaScript, which handles behaviour.
We’ll keep this small and practical. By the end, the CV will have a dark mode toggle that remembers your choice, a footer that updates its own year, and a skills list built from data. Along the way we’ll see the core idea that underpins every modern front-end framework.
Step 1: Add the script
In my-cv, create a file called script.js. Then, at the very end of the <body> in index.html, just before the closing </body> tag, add:
<script src="script.js"></script>
We put it at the end so the HTML elements exist by the time the script runs. Otherwise, the script would look for things that haven’t been created yet and come back empty-handed.
While you’re editing index.html, add this to the footer, giving the year a home:
<footer>
<p>© <span id="year"></span> Alex Morgan</p>
</footer>
Step 2: Talk to the page with the DOM
When a browser loads your HTML, it builds a live model of the page in memory called the DOM (Document Object Model). It’s a tree of objects, one for each element. JavaScript can find those objects, read them, change them, add them, and remove them, and the page updates immediately.
Put this in script.js:
document.getElementById("year").textContent = new Date().getFullYear();
Refresh the page. The footer now shows the current year, and you’ll never have to edit it again. Let’s break the line down:
documentis the DOM’s entry point.getElementById("year")finds the element whoseidisyear. This is why we’ve been addingidattributes..textContentis the text inside that element, which we set.new Date().getFullYear()asks the browser for today’s date and pulls out the year.
If nothing appears, open the console (F12, then the Console tab). It will usually tell you exactly what went wrong and on which line.
Step 3: Build the skills list from data
Now the important bit. We left an empty list in the HTML: <ul id="skills-list" class="tags"></ul>. Add this to script.js:
const skills = ["HTML", "CSS", "JavaScript", "Git", "Azure", "SQL"];
const skillsList = document.getElementById("skills-list");
skills.forEach(function (skill) {
const item = document.createElement("li");
item.textContent = skill;
skillsList.appendChild(item);
});
Refresh, and your skills appear as the pills we styled in Part 2. Here’s what happened:
skillsis an array, which is simply a list of values.forEachruns a function once for each item.- For each skill,
createElementmakes a new<li>, we fill it with text, andappendChildadds it to the list.
To add a skill, you change one line of data and the page updates. You never touch the HTML.
This is the seed of every front-end framework. The page is a function of your data. React, Vue, Angular and Svelte all take that idea, add clever machinery for updating only what changed, and give you a nicer way to write it. But underneath they’re still creating elements and adding them to the DOM, exactly as you just did. Once you’ve done it by hand, the frameworks stop looking like magic.
Step 4: Respond to the user with events
Let’s make the button work. Browsers announce things that happen (clicks, key presses, form submissions) as events, and you attach a function to run when they occur.
First, we need the CSS for dark mode. Remember those custom properties from Part 2? We’re going to redefine them. Add this to style.css, after the :root block:
body.dark {
--bg: #121820;
--text: #e4e7eb;
--muted: #9aa5b1;
--accent: #63b3ed;
--card: #1c2733;
}
When <body> has the class dark, those values override the light ones, and everything that uses var(--bg) and the rest changes at once. No other CSS needs touching. That’s the payoff for using variables.
Now the JavaScript, added to script.js:
const toggle = document.getElementById("theme-toggle");
function applyTheme(isDark) {
document.body.classList.toggle("dark", isDark);
toggle.textContent = isDark ? "Light mode" : "Dark mode";
}
toggle.addEventListener("click", function () {
const isDark = !document.body.classList.contains("dark");
applyTheme(isDark);
});
Click the button and the page switches theme. classList.toggle("dark", isDark) adds the class when isDark is true and removes it when false. The ? : in the middle is a compact if/else called a ternary.
Step 5: Remember the choice
It’s annoying when a site forgets you prefer dark mode the moment you reload. Browsers give us localStorage, a small key-value store that persists between visits. Update the click handler and add one line to apply the saved theme on load:
applyTheme(localStorage.getItem("theme") === "dark");
toggle.addEventListener("click", function () {
const isDark = !document.body.classList.contains("dark");
applyTheme(isDark);
localStorage.setItem("theme", isDark ? "dark" : "light");
});
The first line runs when the page loads and applies whatever was saved last time. If nothing is saved yet, it defaults to light.
One caveat: localStorage is per-browser and per-site, and visitors can clear it. It’s fine for a preference like this, but never store anything sensitive there.
The finished script.js
Here’s the complete file:
// 1. Put the current year in the footer
document.getElementById("year").textContent = new Date().getFullYear();
// 2. Build the skills list from data
const skills = ["HTML", "CSS", "JavaScript", "Git", "Azure", "SQL"];
const skillsList = document.getElementById("skills-list");
skills.forEach(function (skill) {
const item = document.createElement("li");
item.textContent = skill;
skillsList.appendChild(item);
});
// 3. Dark mode toggle, remembered between visits
const toggle = document.getElementById("theme-toggle");
function applyTheme(isDark) {
document.body.classList.toggle("dark", isDark);
toggle.textContent = isDark ? "Light mode" : "Dark mode";
}
applyTheme(localStorage.getItem("theme") === "dark");
toggle.addEventListener("click", function () {
const isDark = !document.body.classList.contains("dark");
applyTheme(isDark);
localStorage.setItem("theme", isDark ? "dark" : "light");
});
Where we are
Your my-cv folder now contains exactly three files: index.html, style.css and script.js. That’s a complete website. There’s no build step, no node_modules, no framework and no dependencies. The whole thing is a few kilobytes and works by opening it in a browser.
That last point matters for the rest of the series. A site like this needs no server-side code, and it just needs somewhere to put the files and serve them to browsers. That’s what hosting is, and there are more ways to do it on Azure than you might expect, with real differences in cost and speed.
Next: Part 4, Where Should It Live? Azure Hosting Options Compared.