Skip to content

Add JavaScript Interactivity

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.


If you are following the three-file approach, open script.js and write everything here.


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.

// Find one element — returns the first match
const nav = document.querySelector('#navbar');
// Find multiple elements — returns all matches as a list
const 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.

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.


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.


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.


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.


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 script.js and refresh the page in your browser. Try each behavior in order:

  1. Scroll down slowly — watch for the nav shadow to appear after the first few pixels
  2. Click a nav link — it should glide to the section rather than jump
  3. Click Send Message with empty fields — the error message should appear in red
  4. 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.


  • querySelector finds page elements using the same selector syntax as CSS
  • addEventListener watches for browser events and runs your code when they fire
  • event.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

Proceed to Test Your Website — one final walkthrough to make sure everything works together the way it should.

  1. 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.