![]()
Chapter 7 — Add Interactivity With JavaScript
Introduction
A website can look beautiful without JavaScript, but modern templates often need interaction.
When a visitor opens a mobile menu, expands an FAQ, clicks a tab, submits a form, closes a notification or interacts with a small calculator, something needs to respond to that action.
That is where JavaScript becomes useful.
In the previous chapters, HTML provided the structure and CSS created the visual appearance. Now JavaScript will provide behavior.
The basic relationship is:
HTML → What the element is
CSS → How the element looks
JavaScript → What the element does
For a commercial weight-loss website template, JavaScript should be used carefully. The goal is not to add dozens of flashy effects. The goal is to improve usability and create useful interactions.
A good template feels responsive without feeling complicated.
7.1 Create the JavaScript File
Inside your project folder, create:
js/
└── script.js
Then connect it to your HTML:
<script src="js/script.js" defer></script>
The defer attribute allows the browser to load the script without unnecessarily blocking the HTML parser.
This is a simple but useful performance practice.
7.2 Start With Strict JavaScript
At the beginning of your JavaScript file, you can use:
'use strict';
This helps catch certain programming mistakes.
Then write your code in small sections.
For example:
'use strict';
document.addEventListener('DOMContentLoaded', () => {
// Website functionality goes here
});
This ensures your code executes after the document has been parsed.
7.3 Build the Mobile Navigation
One of the most important interactions is the mobile menu.
Your HTML might contain:
<button
class="menu-toggle"
type="button"
aria-label="Open navigation menu"
aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
Your navigation might have:
<nav class="main-navigation" id="site-navigation">
Connect the two:
const menuButton = document.querySelector('.menu-toggle');
const navigation = document.querySelector('.main-navigation');
if (menuButton && navigation) {
menuButton.addEventListener('click', () => {
const isOpen = navigation.classList.toggle('active');
menuButton.setAttribute(
'aria-expanded',
String(isOpen)
);
});
}
Now clicking the button adds or removes the active class.
CSS controls what the active menu looks like.
JavaScript controls when that state changes.
7.4 Improve the Mobile Menu Label
The button should communicate its current state.
menuButton.addEventListener('click', () => {
const isOpen = navigation.classList.toggle('active');
menuButton.setAttribute(
'aria-expanded',
String(isOpen)
);
menuButton.setAttribute(
'aria-label',
isOpen ? 'Close navigation menu' : 'Open navigation menu'
);
});
This is a small improvement that makes the component clearer.
7.5 Close the Menu When a Link Is Selected
On mobile, visitors usually expect the menu to disappear after selecting a page.
const navLinks = navigation.querySelectorAll('a');
navLinks.forEach(link => {
link.addEventListener('click', () => {
navigation.classList.remove('active');
menuButton.setAttribute(
'aria-expanded',
'false'
);
menuButton.setAttribute(
'aria-label',
'Open navigation menu'
);
});
});
This creates a smoother navigation experience.
7.6 Close the Menu With Escape
Keyboard interaction is important.
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
navigation.classList.remove('active');
menuButton.setAttribute(
'aria-expanded',
'false'
);
menuButton.setAttribute(
'aria-label',
'Open navigation menu'
);
}
});
Now the visitor can press Escape to close the menu.
7.7 Build an FAQ Accordion
In Chapter 6, we used the native HTML <details> element.
That means you do not actually need JavaScript to make the FAQ work.
This is an important lesson:
Do not use JavaScript when HTML already provides the functionality you need.
However, if you want a custom accordion where only one question can remain open at a time, JavaScript can help.
Example:
const faqItems = document.querySelectorAll('.faq-item');
faqItems.forEach(item => {
item.addEventListener('toggle', () => {
if (item.open) {
faqItems.forEach(otherItem => {
if (otherItem !== item) {
otherItem.open = false;
}
});
}
});
});
This creates a one-open-at-a-time FAQ.
7.8 Create a Back-to-Top Button
Long pages can benefit from a back-to-top button.
HTML:
<button
class="back-to-top"
type="button"
aria-label="Back to top">
↑
</button>
JavaScript:
const backToTop = document.querySelector('.back-to-top');
if (backToTop) {
window.addEventListener('scroll', () => {
if (window.scrollY > 500) {
backToTop.classList.add('visible');
} else {
backToTop.classList.remove('visible');
}
});
backToTop.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
CSS can control the visibility.
.back-to-top {
opacity: 0;
pointer-events: none;
}
.back-to-top.visible {
opacity: 1;
pointer-events: auto;
}
Keep such controls subtle.
They should help visitors, not dominate the page.
7.9 Add a Sticky Header
A sticky header can improve navigation on long pages.
CSS can often handle this without JavaScript:
.site-header {
position: sticky;
top: 0;
}
This is another important principle:
Use CSS whenever CSS is sufficient.
Do not write JavaScript for something the browser can already do efficiently.
7.10 Add a Header Scroll State
If you want the header to change appearance after scrolling, JavaScript can add a class.
const header = document.querySelector('.site-header');
if (header) {
window.addEventListener('scroll', () => {
if (window.scrollY > 20) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
});
}
Then CSS:
.site-header.scrolled {
box-shadow: var(--shadow-sm);
}
The result is a subtle visual change after the visitor begins scrolling.
7.11 Create a Simple Tabs Component
Suppose your resource page has categories:
Nutrition
Movement
Habits
You can create tabs.
HTML:
<div class="tabs">
<div class="tab-buttons" role="tablist">
<button
class="tab-button active"
type="button"
role="tab"
aria-selected="true"
data-tab="nutrition">
Nutrition
</button>
<button
class="tab-button"
type="button"
role="tab"
aria-selected="false"
data-tab="movement">
Movement
</button>
<button
class="tab-button"
type="button"
role="tab"
aria-selected="false"
data-tab="habits">
Habits
</button>
</div>
<div class="tab-panel active" data-panel="nutrition">
Nutrition resources go here.
</div>
<div class="tab-panel" data-panel="movement">
Movement resources go here.
</div>
<div class="tab-panel" data-panel="habits">
Healthy habit resources go here.
</div>
</div>
JavaScript:
const tabButtons = document.querySelectorAll('.tab-button');
const tabPanels = document.querySelectorAll('.tab-panel');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const target = button.dataset.tab;
tabButtons.forEach(item => {
item.classList.remove('active');
item.setAttribute('aria-selected', 'false');
});
tabPanels.forEach(panel => {
panel.classList.remove('active');
});
button.classList.add('active');
button.setAttribute('aria-selected', 'true');
const panel = document.querySelector(
`[data-panel="${target}"]`
);
if (panel) {
panel.classList.add('active');
}
});
});
This is a reusable component.
7.12 Add Search Functionality
A blog template may contain a search field.
HTML:
<form class="search-form" role="search">
<label for="site-search">
Search articles
</label>
<input
type="search"
id="site-search"
placeholder="Search...">
<button type="submit">
Search
</button>
</form>
For a static template, JavaScript can provide a simple client-side demonstration.
For example, if article cards have a class:
<article class="article-card">
you can filter them.
const searchForm = document.querySelector('.search-form');
const searchInput = document.querySelector('#site-search');
const articles = document.querySelectorAll('.article-card');
if (searchForm && searchInput && articles.length) {
searchForm.addEventListener('submit', (event) => {
event.preventDefault();
const query = searchInput.value
.trim()
.toLowerCase();
articles.forEach(article => {
const content = article.textContent.toLowerCase();
article.hidden =
query !== '' && !content.includes(query);
});
});
}
This is useful for a demo.
A production WordPress template would normally use WordPress’s search system instead.
7.13 Create a Search Empty State
If no articles match, display a message.
HTML:
<p class="search-empty" hidden>
No articles found. Try another search term.
</p>
JavaScript:
const emptyMessage =
document.querySelector('.search-empty');
function updateSearchResults() {
let visibleCount = 0;
articles.forEach(article => {
if (!article.hidden) {
visibleCount++;
}
});
if (emptyMessage) {
emptyMessage.hidden = visibleCount !== 0;
}
}
Call the function after filtering.
This creates a more professional experience.
7.14 Add Form Validation
HTML already provides basic validation.
For example:
<input
type="email"
required>
The browser checks whether the field is filled and resembles an email address.
JavaScript should add value only where necessary.
For example, you can display a custom message:
const newsletterForm =
document.querySelector('.newsletter-form');
if (newsletterForm) {
newsletterForm.addEventListener('submit', (event) => {
const email =
newsletterForm.querySelector('input[type="email"]');
if (!email || !email.value.trim()) {
event.preventDefault();
email?.focus();
}
});
}
Do not make validation unnecessarily complicated.
7.15 Create a Success Message
For a static demonstration, you might show a success state:
<p class="form-success" hidden>
Thank you for subscribing.
</p>
JavaScript:
if (newsletterForm) {
newsletterForm.addEventListener('submit', (event) => {
event.preventDefault();
const success =
newsletterForm.querySelector('.form-success');
if (success) {
success.hidden = false;
}
});
}
However, remember that this does not actually subscribe anyone.
A commercial template should clearly document that the buyer must connect the form to a real email service or backend.
7.16 Create a Modal
A modal can be useful for:
- Newsletter signup.
- Resource downloads.
- Contact forms.
- Video previews.
HTML:
<div
class="modal"
id="resource-modal"
hidden>
<div class="modal-overlay"></div>
<div
class="modal-content"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title">
<button
class="modal-close"
type="button"
aria-label="Close dialog">
×
</button>
<h2 id="modal-title">
Get the Free Guide
</h2>
<p>
Enter your email address to continue.
</p>
</div>
</div>
JavaScript can open and close it.
const modal = document.querySelector('#resource-modal');
const modalClose = document.querySelector('.modal-close');
function openModal() {
if (modal) {
modal.hidden = false;
}
}
function closeModal() {
if (modal) {
modal.hidden = true;
}
}
modalClose?.addEventListener('click', closeModal);
The modal can later be connected to a CTA button.
7.17 Close a Modal With Escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && modal && !modal.hidden) {
closeModal();
}
});
This creates a familiar interaction pattern.
7.18 Avoid Overusing Popups
Popups can increase conversions in some contexts, but they can also annoy visitors.
Do not automatically show a large popup immediately after page load unless there is a clear reason.
A better approach is to make the popup:
- Easy to close.
- Clearly relevant.
- Mobile-friendly.
- Accessible.
- Limited in frequency.
The visitor should always remain in control.
7.19 Create a Simple Calorie Calculator Carefully
A weight-loss template might include a calculator.
However, this requires extra care.
A calculator can be presented as an educational tool, not as medical advice.
For example, inputs might include:
- Age.
- Height.
- Weight.
- Activity level.
You could calculate an estimated energy requirement using a standard formula.
But the template should include appropriate educational disclaimers and should avoid presenting the result as a guaranteed prescription.
For a commercial demo, it may be better to label it:
Estimated Daily Energy Calculator
rather than:
Your Exact Calorie Requirement
The wording matters.
7.20 Use JavaScript for UI, Not Medical Decisions
This distinction is extremely important.
A website template can provide:
- Educational calculators.
- Habit trackers.
- Progress interfaces.
- Resource filters.
- Recipe filters.
- Content search.
But a generic website should not pretend to diagnose diseases or prescribe individualized medical treatment.
The template should remain educational and customizable.
7.21 Create a Habit Tracker
A simple habit tracker can be an attractive feature.
HTML:
<div class="habit-list">
<label>
<input type="checkbox" class="habit-check">
Drink enough water
</label>
<label>
<input type="checkbox" class="habit-check">
Take a daily walk
</label>
<label>
<input type="checkbox" class="habit-check">
Prepare balanced meals
</label>
</div>
JavaScript:
const habits =
document.querySelectorAll('.habit-check');
habits.forEach(habit => {
habit.addEventListener('change', () => {
habit.parentElement.classList.toggle(
'completed',
habit.checked
);
});
});
CSS:
.habit-list label.completed {
text-decoration: line-through;
opacity: .6;
}
This provides a simple interactive feature without requiring a database.
7.22 Store Simple Preferences Locally
You can use localStorage for simple non-sensitive preferences.
For example:
habit.addEventListener('change', () => {
const completed =
Array.from(habits)
.map(item => item.checked);
localStorage.setItem(
'habitProgress',
JSON.stringify(completed)
);
});
Then restore the state:
const saved =
JSON.parse(
localStorage.getItem('habitProgress') || '[]'
);
habits.forEach((habit, index) => {
habit.checked = Boolean(saved[index]);
});
This means the browser can remember the user’s selections.
Do not use local storage for sensitive personal information.
7.23 Add a Progress Indicator
A visual progress indicator can make a habit tracker more engaging.
HTML:
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
JavaScript:
function updateProgress() {
const total = habits.length;
const completed =
Array.from(habits)
.filter(item => item.checked)
.length;
const percentage =
total ? (completed / total) * 100 : 0;
const progress =
document.querySelector('.progress-fill');
if (progress) {
progress.style.width = `${percentage}%`;
}
}
Call updateProgress() whenever a checkbox changes.
This creates a satisfying visual feedback mechanism.
7.24 Add Lazy Loading to Images
Modern browsers support:
<img
src="images/article.jpg"
alt="Healthy meal"
loading="lazy">
For images lower on the page, lazy loading can reduce initial loading work.
Do not necessarily lazy-load the primary hero image if it is visible immediately.
The main image may benefit from being loaded quickly.
7.25 Handle Broken Images
During template development, image paths may accidentally be incorrect.
A simple JavaScript fallback is possible:
const images =
document.querySelectorAll('img');
images.forEach(image => {
image.addEventListener('error', () => {
image.classList.add('image-error');
});
});
However, the preferred solution is to fix incorrect paths and ensure that required template assets are included.
Do not rely on error handling to hide missing files.
7.26 Keep JavaScript Modular
As your script grows, avoid creating one enormous function.
Organize functionality logically:
function initNavigation() {
// navigation
}
function initFAQ() {
// FAQ
}
function initSearch() {
// search
}
function initNewsletter() {
// newsletter
}
function initHabitTracker() {
// habit tracker
}
Then:
document.addEventListener('DOMContentLoaded', () => {
initNavigation();
initFAQ();
initSearch();
initNewsletter();
initHabitTracker();
});
This is much easier to maintain.
7.27 Avoid Global Variables
Instead of creating many global variables:
var menu;
var button;
var modal;
var search;
keep related values inside functions.
This reduces conflicts.
Modern JavaScript provides:
const
let
Use const when a variable does not need reassignment.
Use let when it does.
Avoid var in modern projects unless there is a specific reason.
7.28 Use Event Delegation When Appropriate
If a page contains many similar elements, event delegation can simplify event handling.
For example:
document.addEventListener('click', (event) => {
const button =
event.target.closest('[data-action]');
if (!button) {
return;
}
const action =
button.dataset.action;
if (action === 'close') {
// close component
}
});
This technique becomes especially useful in larger templates.
7.29 Handle JavaScript Errors Gracefully
A commercial template should not break simply because one optional component is missing.
Use checks:
const modal =
document.querySelector('#resource-modal');
if (modal) {
// Modal code
}
This allows the buyer to remove the modal HTML without causing JavaScript errors elsewhere.
That is a small but valuable detail in reusable template development.
7.30 Test Without JavaScript
Turn JavaScript off temporarily.
Ask:
- Does the content remain readable?
- Does the navigation still exist?
- Do important links remain accessible?
- Does the FAQ still work if using
<details>? - Does the page remain understandable?
Not every interaction must work without JavaScript, but essential content should not disappear unnecessarily.
7.31 Test the Console
Open the browser developer tools and check the Console.
Look for errors such as:
Uncaught TypeError
or:
Cannot read properties of null
These errors often indicate that your JavaScript expects an element that does not exist.
Fix console errors before delivering the template.
7.32 Test the Mobile Menu Again
Now test:
- Desktop navigation.
- Mobile menu.
- Menu open state.
- Menu close state.
- Link selection.
- Escape key.
- Keyboard focus.
- Screen resizing.
Resize the browser while the menu is open.
Make sure the navigation does not become trapped in an incorrect state.
7.33 Test the FAQ
Check:
- Can the user open an answer?
- Can the user close it?
- Can the keyboard operate it?
- Does the page shift naturally?
- Does only one answer remain open if that is your intended design?
Do not make the FAQ visually attractive at the expense of usability.
7.34 Test Forms
Try:
Empty form
Invalid email
Valid email
Very long email
Mobile screen
Keyboard navigation
The form should respond predictably.
If the template is only a frontend demonstration, clearly document where the buyer must connect the backend or email provider.
7.35 Document Your JavaScript
Add comments where useful.
For example:
// Mobile navigation toggle
function initNavigation() {
// ...
}
Do not comment every obvious line.
Good comments explain why something is done when the reason is not obvious.
7.36 Create a JavaScript Checklist
Before completing this stage:
- Mobile navigation works.
- Menu state is communicated.
- Escape closes the menu.
- FAQ works.
- Search works if included.
- Forms validate appropriately.
- Modal opens and closes if included.
- Habit tracker works if included.
- Local storage works if included.
- No console errors.
- Missing optional elements do not break the script.
- Keyboard interaction works.
- Mobile interactions are usable.
Conclusion
JavaScript gives your template life.
But professional JavaScript is not about adding as many effects as possible. It is about creating meaningful interactions that improve the visitor’s experience.
The best commercial template often uses surprisingly little JavaScript because HTML and CSS already provide much of the required functionality.
Use:
HTML for structure.
CSS for presentation.
JavaScript for behavior.
This separation keeps the project clean and makes it easier for buyers to customize.
Your template now has a visual design and interactive functionality. The next challenge is equally important: making the website fast, accessible, search-friendly and technically polished.
That is the focus of the next chapter.


