Creating a country search feature using HTML and JavaScript involves creating a simple user interface where users can input a search term to find a country. Here’s a step-by-step guide to building this feature:
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Country Search & Filter App</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" /> <link rel="stylesheet" href="style.css" /> <script src="script.js" defer></script> </head> <body> <div class="container"> <!-- search --> <div class="search"> <input type="search" id="search" placeholder="Search country" /> </div> <!-- filter --> <div class="filter"> <button>All</button> <div class="options"></div> </div> <!-- grid --> <div class="grid-container"></div> <!-- result --> <div class="no-result">No result found.</div> <!-- modal --> <div class="modal"> <div class="modal-content"> <button data-dismiss> <span class="material-symbols-outlined">close</span> </button> <figure> <img src="https://placehold.co/600x400/purple/white?text=FLAG" alt="" /> <figcaption>Country Name</figcaption> </figure> </div> </div> </div> </body> </html>
CSS (styles.css)
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap"); :root { --color0: goldenrod; --color1: #082032; --color2: #2c394b; --color3: #9db2bf; --color4: #dde6ed; } * { font-family: "Poppins", sans-serif; padding: 0; margin: 0; box-sizing: border-box; } body { background: var(--color1); color: var(--color3); } img { width: 100%; height: auto; display: block; } /* layout */ .container { width: 90%; max-width: 1200px; margin: 2rem auto 1rem; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2rem; } .search, .filter { flex-basis: calc(50% - 1rem); } .grid-container { flex-basis: 100%; } /* search */ .search input { width: 100%; max-width: 400px; padding: 10px; padding-left: 20px; border: none; border-radius: 5px; background: var(--color2); color: var(--color4); outline-color: var(--color0); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25); } .search input::placeholder { color: var(--color3); } .search input::-webkit-search-cancel-button { -webkit-appearance: none; display: inline-block; width: 12px; height: 12px; margin-left: 10px; background: linear-gradient( 45deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0) 43%, var(--color0) 45%, var(--color0) 55%, rgba(0, 0, 0, 0) 57%, rgba(0, 0, 0, 0) 100% ), linear-gradient( 135deg, transparent 0%, transparent 43%, var(--color0) 45%, var(--color0) 55%, transparent 57%, transparent 100% ); } /* filter */ .filter { width: 100%; max-width: 250px; border-radius: 5px; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25); position: relative; } .filter button { width: 100%; height: 100%; cursor: pointer; border: none; border-radius: inherit; background: var(--color2); color: var(--color3); } .filter button:hover { color: var(--color4); } .filter button::after { font-family: "Material Symbols Outlined"; content: "expand_more"; width: 24px; height: 24px; font-size: 24px; line-height: 1; position: absolute; top: 50%; right: 10px; transform: translateY(-50%); } .filter.open button::after { content: "expand_less"; } .filter .options { position: absolute; top: calc(100% + 2px); z-index: 99; background: var(--color2); width: 100%; border-radius: inherit; box-shadow: inherit; overflow: hidden; opacity: 0; transform: scaleY(0); transform-origin: top; transition: 0.2s; } .filter.open .options { opacity: 1; transform: scaleY(1); } .filter .options div { padding: 0.5rem 1rem; cursor: pointer; } .filter .options div:hover { background: var(--color1); color: var(--color4); } /* grid */ .grid-container { display: grid; grid-template-columns: auto; gap: 2rem; } .grid-container + .no-result { display: none; width: 100%; text-align: center; } .grid-container:empty + .no-result { display: block; } .grid-item { background: var(--color2); border-radius: 10px; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25); overflow: hidden; } .grid-item a { display: block; } .grid-item img { width: 100%; height: 200px; object-fit: cover; background: #ddd; } .grid-item div { padding: 1.5rem; } .grid-item h3 { font-size: 1rem; font-weight: 600; text-transform: uppercase; color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); margin-bottom: 0.5rem; } .grid-item p { margin-bottom: 0.5rem; } .grid-item label { display: block; color: var(--color0); font-size: 0.85rem; font-weight: 600; text-transform: uppercase; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); } @media (min-width: 768px) { .grid-container { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 1024px) { .grid-container { grid-template-columns: repeat(3, 1fr); } } /* modal */ .modal { position: fixed; top: 0; left: 0; z-index: 1024; width: 100%; height: 100%; background: var(--color1); display: none; animation-name: fadeIn; animation-duration: 0.3s; } .modal.show { display: grid; place-items: center; } .modal img { max-width: 80%; max-height: 80vh; margin: auto; filter: drop-shadow(0 5px 10px rgba(0, 0, 0, 0.25)); } .modal figcaption { margin-top: 1.5rem; text-align: center; font-size: 0.9rem; font-weight: 500; text-transform: uppercase; color: var(--color0); } .modal button[data-dismiss] { position: absolute; top: 1.5rem; right: 1.5rem; width: 50px; height: 50px; border: none; border-radius: 50%; cursor: pointer; background: transparent; color: var(--color3); } .modal button[data-dismiss]:hover { color: var(--color4); } .modal button[data-dismiss] span { font-size: 42px; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
JavaScript (script.js)
const API_URL = "https://restcountries.com/v3.1/all"; const FIELDS = "name,cca3,currencies,capital,region,population,continents,flags"; const filter = document.querySelector(".filter"); const gridContainer = document.querySelector(".grid-container"); const searchInput = document.getElementById("search"); const modal = document.querySelector(".modal"); //* variables let countries = []; let filteredCountries = []; let search = null; let continent = "All"; //* api functions const fetchData = async (apiURL) => { try { const response = await fetch(apiURL); return await response.json(); } catch (error) { console.error("Error fetching data", error); throw error; } }; const getCountries = async () => { try { const apiURL = `${API_URL}?fields=${FIELDS}`; const data = await fetchData(apiURL); return data.sort((a, b) => { const nameA = a.name.common.toLowerCase(); const nameB = b.name.common.toLowerCase(); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; }); } catch (error) { return []; } }; //* render functions const renderCountryList = () => { // use fragment to temporary build the list const fragment = document.createDocumentFragment(); filteredCountries.forEach((country) => { // destructure const { name, flags, cca3, capital, population, currencies, region, continents, } = country; // change currencies from object to array const currencyList = Object.keys(currencies).map((code) => { const { name } = currencies[code]; return `${name} (${code})`; }); const gridItem = document.createElement("div"); gridItem.classList.add("grid-item"); gridItem.addEventListener("click", onGridItemClick); gridItem.innerHTML = ` <a href="${flags.svg}"> <img src="${flags.svg}" alt="${name.common} (${cca3})" /> </a> <div> <h3>${name.common}</h3> <p> <label>Capital</label> ${capital.join(", ")} </p> <p> <label>Population</label> ${population.toLocaleString("en")} </p> <p> <label>Currencies</label> ${currencyList.join(", ")} </p> <p> <label>Region</label> ${region} </p> <p> <label>Continent</label> ${continents.join(", ")} </p> </div>`; fragment.appendChild(gridItem); }); // add the virtual list to DOM gridContainer.replaceChildren(fragment); }; const renderFilterList = () => { const optionList = filter.querySelector(".options"); // get the continents from countries and sort alphabetically let continents = countries.flatMap((c) => c.continents).sort(); // prepend 'All' and remove duplicates continents = ["All", ...new Set(continents)]; continents.forEach((continent) => { const option = document.createElement("div"); option.textContent = continent; option.addEventListener("click", onFilterOptionClick); optionList.appendChild(option); }); }; //* event handlers const loadCountries = async () => { countries = await getCountries(); filteredCountries = countries; renderCountryList(); renderFilterList(); }; const handleEscapeKey = (e) => { if (e.key === "Escape") { modal.classList.remove("show"); } }; const applyFilters = () => { filteredCountries = countries; if (search !== null) { filteredCountries = filteredCountries.filter((country) => { // name, capital, code const name = country.name.common.toLowerCase(); const capital = country.capital.join(", ").toLowerCase(); const code = country.cca3.toLowerCase(); return ( name.includes(search) || capital.includes(search) || code.includes(search) ); }); } if (continent !== "All") { filteredCountries = filteredCountries.filter((country) => { return country.continents.includes(continent); }); } }; const onGridItemClick = (e) => { e.preventDefault(); if (e.target.tagName === "IMG") { const { src, alt } = e.target; const img = modal.querySelector("img"); const dismiss = modal.querySelector("[data-dismiss]"); const caption = modal.querySelector("figcaption"); img.src = src; img.alt = alt; caption.textContent = alt; dismiss.addEventListener("click", onDismissClick); modal.classList.add("show"); } }; const onDismissClick = () => { modal.classList.remove("show"); }; const onFilterOptionClick = (e) => { continent = e.target.textContent; // Change the selected filter const button = filter.querySelector("button"); button.textContent = continent; // apply the search filter to filtered countries applyFilters(); // re-render country list renderCountryList(); }; const onFilterButtonClick = () => { filter.classList.toggle("open"); }; const onSearchInput = () => { search = searchInput.value.trim().toLowerCase(); // apply the search keyword to filtered countries applyFilters(); // re-render country list renderCountryList(); }; //* event listeners document.addEventListener("DOMContentLoaded", loadCountries); document.addEventListener("keydown", handleEscapeKey); filter.addEventListener("click", onFilterButtonClick); searchInput.addEventListener("input", onSearchInput);
Explanation
- HTML Structure:
- A simple HTML structure includes a container with an input field for the search term and an unordered list to display the filtered country names.
- CSS Styling:
- The CSS styles the container and input elements for a clean, centered look.
- The unordered list (
ul
) and list items (li
) are styled to remove default styling and add custom borders and padding.
- JavaScript Functionality:
- A list of country names is stored in an array.
- An event listener is added to the search input to filter the country names based on the user input.
- The
input
event triggers a function that filters the country array and updates the unordered list (ul
) with the filtered results.
How to Run
- Create three files:
index.html
,styles.css
, andscript.js
. - Copy the respective code blocks into these files.
- Open the
index.html
file in a web browser to see the country search feature in action.
This simple implementation allows users to type in a search term and see a list of matching country names in real-time.
Reviews
There are no reviews yet. Be the first one to write one.