Skip to content

JavaScript Tutorial JavaScript Practice Challenge

Practice: DOM and Event Challenges

Lesson 256 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

Each challenge says what you should see when it works. Try it with the starter code, then compare with the solution.

Challenge 1: toggle dark mode

Make the button toggle a dark class on the page body. The text should switch between “Dark mode” and “Light mode”.

Expected: each click switches the background colour and the button text.

index.html
HTML
<style>
  body { font-family: system-ui, sans-serif; padding: 12px; }
  body.dark { background: #1c1b1f; color: #eee; }
</style>
<button id="theme">Dark mode</button>
Show a possible solution
index.html
HTML
<style>
  body { font-family: system-ui, sans-serif; padding: 12px; }
  body.dark { background: #1c1b1f; color: #eee; }
</style>
<button id="theme" aria-pressed="false">Dark mode</button>
<script>
  const button = document.querySelector("#theme");
  button.addEventListener("click", () => {
    const dark = document.body.classList.toggle("dark");
    button.textContent = dark ? "Light mode" : "Dark mode";
    button.setAttribute("aria-pressed", String(dark));
  });
</script>

Challenge 2: character counter

Show “n / 40” under a textarea that updates as the user types. Turn the text red when fewer than 5 characters remain.

Expected: typing 36 characters shows 36 / 40 in red.

Show a possible solution
index.html
HTML
<textarea id="t" maxlength="40" aria-describedby="c"></textarea>
<p id="c">0 / 40</p>
<script>
  const t = document.querySelector("#t");
  const c = document.querySelector("#c");
  t.addEventListener("input", () => {
    c.textContent = `${t.value.length} / 40`;
    c.style.color = 40 - t.value.length < 5 ? "crimson" : "";
  });
</script>

Challenge 3: a live clock

Show the current time and update it every second. Stop the interval when the button is pressed.

Expected: the time ticks, then freezes after pressing the button.

Show a possible solution
index.html
HTML
<p id="clock"></p>
<button id="stop">Stop</button>
<script>
  const clock = document.querySelector("#clock");
  const tick = () => { clock.textContent = new Date().toLocaleTimeString(); };

  tick();
  const timer = setInterval(tick, 1000);

  document.querySelector("#stop").addEventListener("click", () => clearInterval(timer));
</script>

Challenge 4: delete from a list with delegation

Given a list of items with a “x” button each, delete the item when its button is clicked, using one event listener.

Expected: clicking any “x” removes its list item. New items you add also work.

Show a possible solution
index.html
HTML
<ul id="list">
  <li>Apples <button>x</button></li>
  <li>Bread <button>x</button></li>
</ul>
<button id="add">Add item</button>
<script>
  const list = document.querySelector("#list");

  list.addEventListener("click", event => {
    const button = event.target.closest("button");
    if (button) button.closest("li").remove();
  });

  document.querySelector("#add").addEventListener("click", () => {
    const li = document.createElement("li");
    li.append("New item ", Object.assign(document.createElement("button"), { textContent: "x" }));
    list.append(li);
  });
</script>

Challenge 5: a keyboard shortcut

Pressing / should focus the search field (but only when the user is not already typing in a field). Pressing Escape in the field should clear it.

Expected: press / anywhere on the page, and the cursor jumps to the search box.

Show a possible solution
index.html
HTML
<input id="search" placeholder="Search (press / to focus)">
<script>
  const search = document.querySelector("#search");

  document.addEventListener("keydown", event => {
    const typing = ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName);
    if (event.key === "/" && !typing) {
      event.preventDefault();
      search.focus();
    }
  });

  search.addEventListener("keydown", event => {
    if (event.key === "Escape") search.value = "";
  });
</script>

Challenge 6: show and hide a password

Add a checkbox “Show password” that switches the input between type="password" and type="text".

Expected: checking the box reveals the characters, unchecking hides them again.

Show a possible solution
index.html
HTML
<label>Password <input id="pw" type="password" value="secret123"></label>
<label><input type="checkbox" id="show"> Show password</label>
<script>
  document.querySelector("#show").addEventListener("change", event => {
    document.querySelector("#pw").type = event.target.checked ? "text" : "password";
  });
</script>

Challenge 7: filter a list as you type

Hide the list items that do not contain the text typed in a box, ignoring case.

Expected: typing “an” leaves only items containing “an” (Banana, Mango).

Show a possible solution
index.html
HTML
<input id="q" placeholder="Filter fruit">
<ul id="fruit"><li>Apple</li><li>Banana</li><li>Cherry</li><li>Mango</li></ul>
<script>
  document.querySelector("#q").addEventListener("input", event => {
    const term = event.target.value.toLowerCase();
    document.querySelectorAll("#fruit li").forEach(li => {
      li.hidden = !li.textContent.toLowerCase().includes(term);
    });
  });
</script>

Challenge 8: a form check

A form has an email field. On submit, prevent the page from reloading, check the email with a simple pattern, and show either an error message or “Thanks!” in a status paragraph.

Expected: maya@example.com shows “Thanks!”, while maya shows an error.

Show a possible solution
index.html
HTML
<form id="f" novalidate>
  <label>Email <input id="email" name="email"></label>
  <button>Join</button>
</form>
<p id="status" role="status"></p>
<script>
  document.querySelector("#f").addEventListener("submit", event => {
    event.preventDefault();
    const value = document.querySelector("#email").value.trim();
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    document.querySelector("#status").textContent = valid ? "Thanks!" : "Please enter a valid email address.";
  });
</script>

Next, find the bugs in Debugging Challenges.