Add JavaScript Interactivity
Introduction
Section titled “Introduction”HTML gave your page structure. CSS made it look good. JavaScript is what makes it feel alive — the difference between a poster on a wall and someone actually standing there ready to help.
For this page, that means three things: a nav bar that reacts when you scroll, anchor links that glide smoothly to each section instead of snapping around like a PowerPoint slide transition, and a contact form that checks your work before pretending to send anything.
None of this requires a framework, a library, or a computer science degree. Just a text editor, a browser, and the file you already created in the setup step.
Where Your JavaScript Lives
Section titled “Where Your JavaScript Lives”If you are following the three-file approach, open script.js and write everything here.
How JavaScript Talks to Your Page
Section titled “How JavaScript Talks to Your Page”JavaScript interacts with the page through something called the DOM — the Document Object Model. Think of it as a live map of every element on your page. JavaScript can read that map, find any element, and change it, move it, hide it, or make it respond to clicks and keystrokes.
You do not need to memorize how the DOM works under the hood. You just need two things: a way to find elements, and a way to listen for things happening to them.
Finding Elements
Section titled “Finding Elements”// Find one element — returns the first matchconst nav = document.querySelector('#navbar');
// Find multiple elements — returns all matches as a listconst links = document.querySelectorAll('.nav-links a');querySelector uses the same selector syntax as CSS. If you can target it in
style.css, you can find it in script.js the same way. #navbar finds the element
with id="navbar". .nav-links a finds all anchor tags inside .nav-links.
Listening for Events
Section titled “Listening for Events”Once you have an element, you can tell it to watch for something — a click, a scroll, a keystroke, a form submission — and run a function when it happens:
nav.addEventListener('click', function(event) { console.log('The nav was clicked!');});The first argument is the event name. The second is the function to run when it fires.
The event parameter inside that function is an object the browser hands you with
details about what just happened — which element was clicked, where the mouse was,
whether any keys were held down, and more. You will use it shortly.
Behavior 1 — Nav Shadow on Scroll
Section titled “Behavior 1 — Nav Shadow on Scroll”Right now the nav just sits there. A nice touch is giving it a subtle drop shadow when
the user scrolls down, so the nav feels like it is floating above the content rather
than glued to it. The CSS for this is already written — #navbar.scrolled in
style.css has the shadow ready to go. JavaScript just needs to flip that class on
and off at the right moment.
const navbar = document.querySelector('#navbar');
window.addEventListener('scroll', function() { if (window.scrollY > 10) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); }});window.scrollY is the number of pixels the page has scrolled from the top.
When it is greater than 10, add the scrolled class. When it drops back to 10 or
below, remove it. The CSS handles the rest.
This is a good example of how JavaScript and CSS are meant to work together — JS decides when something changes, CSS decides what it looks like. Keeping those two jobs separate makes both files easier to read and maintain.
Behavior 2 — Smooth Scroll for Anchor Links
Section titled “Behavior 2 — Smooth Scroll for Anchor Links”By default, clicking a nav link jumps to the target section instantly — which works, but feels a bit abrupt. Smooth scrolling animates that jump so the user can see where they are going. It is a small thing, but it makes the whole page feel more polished.
You have two options. The CSS-only version is one line in style.css:
html { scroll-behavior: smooth;}Add that to the top of your stylesheet if you want the quick win. It works in all modern browsers and requires no JavaScript at all.
The JavaScript version gives you more control — useful if you ever want to add an offset so the nav bar does not cover the section heading when you land on it:
const navLinks = document.querySelectorAll('.nav-links a');
navLinks.forEach(function(link) { link.addEventListener('click', function(event) { event.preventDefault();
const targetId = link.getAttribute('href').slice(1); const targetEl = document.querySelector('#' + targetId);
if (targetEl) { const navHeight = navbar.offsetHeight; const targetTop = targetEl.getBoundingClientRect().top + window.scrollY - navHeight - 16;
window.scrollTo({ top: targetTop, behavior: 'smooth' }); } });});event.preventDefault() stops the browser from doing its default jump behavior so
your code can take over. getAttribute('href').slice(1) pulls the #about value off
the link and removes the # to get just about, which is then used to find the
matching section. The navHeight + 16 offset lands the scroll a little below the
sticky nav so the section heading is not hidden behind it.
Behavior 3 — Contact Form Validation
Section titled “Behavior 3 — Contact Form Validation”The contact form looks great and even has a submit button, but right now clicking it either reloads the page or does nothing, depending on the browser. This code intercepts the submission, checks that the fields are filled in correctly, and shows a friendly message either way.
const form = document.querySelector('#contact-form');const feedback = document.querySelector('#form-feedback');
form.addEventListener('submit', function(event) { event.preventDefault();
const name = document.querySelector('#name').value.trim(); const email = document.querySelector('#email').value.trim(); const message = document.querySelector('#message').value.trim();
// Clear any previous feedback feedback.className = 'form-feedback hidden'; feedback.textContent = '';
// Validate fields if (!name || !email || !message) { showFeedback('Please fill in all fields before sending.', 'error'); return; }
if (!isValidEmail(email)) { showFeedback('That email address does not look quite right — please double-check it.', 'error'); return; }
// All good! showFeedback('Message sent! Thanks for reaching out — I will get back to you soon.', 'success'); form.reset();});
function showFeedback(message, type) { feedback.textContent = message; feedback.className = 'form-feedback ' + type;}
function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);}A few things worth knowing here:
event.preventDefault() stops the form from doing its default browser behavior,
which is to reload the page. Once you call this, you are fully in charge of what happens next.
.value.trim() reads the text the user typed and strips any leading or trailing
spaces. This prevents someone from submitting a field that looks filled in but is
actually just a few spacebar presses — which is a surprisingly common thing that
happens, usually by accident.
isValidEmail() uses a regular expression1 to check that the email at least
looks like an email address — something before the @, a domain, and a dot.
It is not trying to be perfect; it is just catching obvious typos like notanemail
or oops@ before they go any further.
form.reset() clears all the fields after a successful submission, which is a nice
courtesy to the user so they are not staring at their own message after it has been sent.
Full script.js Listing
Section titled “Full script.js Listing”Here is the complete file in one place. The three behaviors are grouped with comments so the file is easy to navigate:
/* ============================================= NAVIGATION — SCROLL SHADOW ============================================= */const navbar = document.querySelector('#navbar');
window.addEventListener('scroll', function() { if (window.scrollY > 10) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); }});
/* ============================================= NAVIGATION — SMOOTH SCROLL ============================================= */const navLinks = document.querySelectorAll('.nav-links a');
navLinks.forEach(function(link) { link.addEventListener('click', function(event) { event.preventDefault();
const targetId = link.getAttribute('href').slice(1); const targetEl = document.querySelector('#' + targetId);
if (targetEl) { const navHeight = navbar.offsetHeight; const targetTop = targetEl.getBoundingClientRect().top + window.scrollY - navHeight - 16;
window.scrollTo({ top: targetTop, behavior: 'smooth' }); } });});
/* ============================================= CONTACT FORM — VALIDATION & FEEDBACK ============================================= */const form = document.querySelector('#contact-form');const feedback = document.querySelector('#form-feedback');
form.addEventListener('submit', function(event) { event.preventDefault();
const name = document.querySelector('#name').value.trim(); const email = document.querySelector('#email').value.trim(); const message = document.querySelector('#message').value.trim();
feedback.className = 'form-feedback hidden'; feedback.textContent = '';
if (!name || !email || !message) { showFeedback('Please fill in all fields before sending.', 'error'); return; }
if (!isValidEmail(email)) { showFeedback('That email address does not look quite right — please double-check it.', 'error'); return; }
showFeedback('Message sent! Thanks for reaching out — I will get back to you soon.', 'success'); form.reset();});
function showFeedback(msg, type) { feedback.textContent = msg; feedback.className = 'form-feedback ' + type;}
function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);}Save and Try It Out
Section titled “Save and Try It Out”Save script.js and refresh the page in your browser. Try each behavior in order:
- Scroll down slowly — watch for the nav shadow to appear after the first few pixels
- Click a nav link — it should glide to the section rather than jump
- Click Send Message with empty fields — the error message should appear in red
- Fill everything in correctly and click again — the success message should appear and the form should clear itself
If a behavior is not working, open DevTools (F12), click the Console tab, and look
for any red error messages. JavaScript errors are usually pretty descriptive about what
went wrong and on which line in the script.js file — the console is a friend here, not
something to be afraid of.
What You Learned
Section titled “What You Learned”querySelectorfinds page elements using the same selector syntax as CSSaddEventListenerwatches for browser events and runs your code when they fireevent.preventDefault()stops the browser’s default behavior so your code takes over- JavaScript and CSS work as a team — JS toggles classes, CSS handles what those classes look like
- Client-side form validation catches obvious mistakes before submission, but the actual sending still needs a backend service
Next Steps
Section titled “Next Steps”Proceed to Test Your Website — one final walkthrough to make sure everything works together the way it should.
Footnotes
Section titled “Footnotes”-
A regular expression (or regex) is a pattern used to match text. The one used here —
/^[^\s@]+@[^\s@]+\.[^\s@]+$/— checks for characters before an@, a domain name, and a dot-something at the end. Regex looks intimidating at first glance but is enormously useful once you get comfortable with it. regex101.com is a great place to experiment with patterns and see plain-English explanations of what each part does. ↩