Build an Interactive Image Gallery with HTML, CSS, and JavaScriptNew
• Updated 5/26/2026 • HTMLJavaScriptDOMEvent HandlingCSS
Challenge Goal
In this intermediate coding challenge, you will build an interactive image gallery that lets users filter images by category. You will practice semantic HTML, responsive CSS Grid, DOM manipulation, event handling, and JavaScript data attributes.
What You Will Build
You will create a single HTML file with filter buttons, a responsive image gallery, internal CSS, and internal JavaScript. When a user clicks a category button, only the images that match that category should remain visible.
The gallery will include these categories:
- All
- Nature
- City
- Animals
Concept Overview
This challenge focuses on using JavaScript to update a web page without reloading it. Each filter button stores a category in a data-filter attribute, and each gallery card stores its category in a data-category attribute.
You will practice:
- Structuring a page with semantic HTML elements like
<main>,<section>,<nav>, and<article>. - Building a responsive gallery layout with CSS Grid.
- Using
data-filteranddata-categoryattributes. - Selecting elements with
document.querySelectorAll(). - Listening for button clicks with
addEventListener(). - Showing and hiding elements with
classList.add()andclassList.remove().
Example
When the page first loads, all gallery items should be visible.
Selected filter: all
Visible items: Nature, City, Animals
When the user clicks the Nature button, only items with data-category="nature" should remain visible.
Selected filter: nature
Visible items: Nature only
Hidden items: City, Animals
Coding Challenge
Create an interactive image gallery using a single file named index.html. The CSS should be written inside a <style> tag, and the JavaScript should be written inside a <script> tag.
Requirements
- Create one file named
index.html. - Add filter buttons for
All,Nature,City, andAnimals. - Each filter button must have a
data-filterattribute. - Each gallery item must be wrapped in an
<article>element. - Each gallery item must have a
data-categoryattribute. - Clicking a category button should show only matching gallery items.
- Clicking the
Allbutton should show every gallery item. - Hidden items must use a CSS class named
hidden. - The page must work without external libraries, external CSS files, or external JavaScript files.
Input and Output
Input
The user clicks one of the gallery filter buttons.
Clicked button: City
Output
The gallery updates in the browser and only matching items remain visible.
Visible category: city
Hidden categories: nature, animals
Constraints
- Use only HTML, CSS, and vanilla JavaScript.
- Do not use external libraries or frameworks.
- Do not use external CSS or JavaScript files.
- Do not reload the page when filtering.
- Do not rebuild the gallery with JavaScript; only toggle CSS classes.
- Use meaningful
alttext for all images. - Keep the layout responsive for small and large screens.
Hints
- Select all filter buttons using
document.querySelectorAll("[data-filter]"). - Select all gallery items using
document.querySelectorAll(".gallery article"). - Use
button.dataset.filterto get the selected category. - Use
item.dataset.categoryto get each gallery item's category. - If the selected filter is
all, remove thehiddenclass from every item. - If an item's category does not match the selected filter, add the
hiddenclass. - If an item's category matches the selected filter, remove the
hiddenclass.
Starter Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Image Gallery</title>
<style>
/* Add your CSS here */
</style>
</head>
<body>
<main>
<h1>Interactive Image Gallery</h1>
<nav aria-label="Gallery filters">
<button type="button" data-filter="all">All</button>
<button type="button" data-filter="nature">Nature</button>
<button type="button" data-filter="city">City</button>
<button type="button" data-filter="animals">Animals</button>
</nav>
<section class="gallery" aria-label="Image gallery">
<article data-category="nature">
<img src="https://placehold.co/300x200?text=Nature" alt="Green mountain landscape">
</article>
<article data-category="city">
<img src="https://placehold.co/300x200?text=City" alt="City skyline with buildings">
</article>
<article data-category="animals">
<img src="https://placehold.co/300x200?text=Animals" alt="Animal portrait">
</article>
</section>
</main>
<script>
const filterButtons = document.querySelectorAll("[data-filter]");
const galleryItems = document.querySelectorAll(".gallery article");
filterButtons.forEach(function(button) {
button.addEventListener("click", function() {
// Add your filtering logic here
});
});
</script>
</body>
</html>
Solution Walkthrough
Step 1: Create the HTML structure
Start with a complete HTML document. Add a heading, a navigation area for the filter buttons, and a gallery section containing image cards.
Step 2: Add internal CSS
Place the CSS inside a <style> tag in the <head>. Use CSS Grid for the gallery layout and create a hidden class with display: none;.
Step 3: Add data attributes
Give each button a data-filter value. Give each gallery item a matching data-category value. These values are what JavaScript will compare.
Step 4: Add internal JavaScript
Place the JavaScript inside a <script> tag before the closing </body> tag.
Step 5: Show or hide gallery items
When a button is clicked, loop through the gallery items. If the selected filter is all or matches the item's category, show the item. Otherwise, hide it.
Solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Image Gallery</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f4f6f8;
color: #1f2937;
}
main {
max-width: 960px;
margin: 0 auto;
padding: 32px 16px;
}
h1,
.intro {
text-align: center;
}
.intro {
margin-bottom: 24px;
color: #4b5563;
}
.filters {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 24px;
}
button {
border: none;
padding: 10px 16px;
border-radius: 8px;
background-color: #2563eb;
color: white;
font-weight: bold;
cursor: pointer;
}
button:hover {
background-color: #1d4ed8;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.gallery article {
background-color: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.gallery img {
width: 100%;
display: block;
}
.gallery h2 {
margin: 0;
padding: 12px;
font-size: 18px;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<main>
<h1>Interactive Image Gallery</h1>
<p class="intro">Click a category to filter the gallery instantly.</p>
<nav aria-label="Gallery filters" class="filters">
<button type="button" data-filter="all">All</button>
<button type="button" data-filter="nature">Nature</button>
<button type="button" data-filter="city">City</button>
<button type="button" data-filter="animals">Animals</button>
</nav>
<section class="gallery" aria-label="Image gallery">
<article data-category="nature">
<img src="https://placehold.co/300x200?text=Nature+1" alt="Green mountain landscape">
<h2>Mountain View</h2>
</article>
<article data-category="city">
<img src="https://placehold.co/300x200?text=City+1" alt="City skyline with buildings">
<h2>City Skyline</h2>
</article>
<article data-category="animals">
<img src="https://placehold.co/300x200?text=Animal+1" alt="Golden dog sitting outdoors">
<h2>Friendly Dog</h2>
</article>
<article data-category="nature">
<img src="https://placehold.co/300x200?text=Nature+2" alt="Forest waterfall scene">
<h2>Forest Waterfall</h2>
</article>
<article data-category="city">
<img src="https://placehold.co/300x200?text=City+2" alt="Bridge lights at night">
<h2>Night Bridge</h2>
</article>
<article data-category="animals">
<img src="https://placehold.co/300x200?text=Animal+2" alt="Cat looking at the camera">
<h2>Curious Cat</h2>
</article>
</section>
</main>
<script>
const filterButtons = document.querySelectorAll("[data-filter]");
const galleryItems = document.querySelectorAll(".gallery article");
filterButtons.forEach(function(button) {
button.addEventListener("click", function() {
const selectedFilter = button.dataset.filter;
galleryItems.forEach(function(item) {
const itemCategory = item.dataset.category;
if (selectedFilter === "all" || selectedFilter === itemCategory) {
item.classList.remove("hidden");
} else {
item.classList.add("hidden");
}
});
});
});
</script>
</body>
</html>
Why This Works
Each button stores its filter value in a data-filter attribute. Each gallery card stores its category in a data-category attribute. When a button is clicked, JavaScript compares those values and toggles the hidden class. This keeps the solution simple because the HTML stays in place and only the visibility changes.
Stretch Goals
- Add an active style to the currently selected filter button.
- Add more gallery items for each category.
- Add a search input that filters images by their
alttext or title. - Add smooth CSS transitions when items appear or disappear.
- Load the gallery data from a JavaScript array instead of writing every item in HTML.
- Add a new category such as Food, Travel, or Technology.
Quick Checklist
- Do all filter buttons have a
data-filterattribute? - Do all gallery items have a
data-categoryattribute? - Does clicking
Allshow every item? - Does clicking a category hide non-matching items?
- Does the page work without reloading?
- Does the final solution use one complete
index.htmlfile?

