Project 01 · Beginner
Counter
A counter with three buttons: Decrease, Reset, and Increase. Each click changes a number kept in JavaScript, the page updates to show it, and screen readers read the new value out.
- 2 HTML ideas
- 3 CSS ideas
- 4 JavaScript ideas
Why build it
Once you can change a number with a click and show it on the page, you can build a like button, a quantity picker for a shop basket, a score keeper for a board game, or a rep counter for a workout. Each one is the loop you build here: someone does something, your data changes, and the page updates to match.
Skills you'll earn
- Find any element on the page with querySelector and change what it says
- Run your own code when someone clicks a button, with addEventListener
- Keep a changing value in a variable and update the page from it
- Make screen readers announce a number each time it changes, with aria-live
01
Try it
Play with it first, then read the code that makes it work.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="counter">
<h1 class="counter-title">Counter</h1>
<!-- aria-live makes screen readers announce the number when it changes. -->
<p class="counter-value" id="count" aria-live="polite">0</p>
<!-- Real <button>s work with a keyboard and screen readers for free. -->
<div class="counter-buttons">
<button type="button" class="counter-button" id="decrease">
Decrease
</button>
<button type="button" class="counter-button" id="reset">Reset</button>
<button type="button" class="counter-button" id="increase">
Increase
</button>
</div>
</main>
<script src="script.js"></script>
</body>
</html>02
How it works
The ideas behind it, one at a time, each next to the code that does it.
HTMLthe structure
- 01
Use real
<button>elementsA
<button>already knows how to be a button. Keyboard users can reach it with Tab and press it with Enter or Space, and screen readers announce it as "button". A<div>styled to look the same does none of that, and you'd have to rebuild each part with extra code.<!-- Real <button>s work with a keyboard and screen readers for free. --> <div class="counter-buttons"> <button type="button" class="counter-button" id="decrease"> Decrease </button> <button type="button" class="counter-button" id="reset">Reset</button> <button type="button" class="counter-button" id="increase"> Increase </button> - 02
Announce changes with
aria-liveSomeone who can't see the screen won't notice the number change unless something tells them.
aria-livemarks an element whose updates screen readers should read out.politemeans they wait until the person pauses, instead of interrupting what's already being read.<!-- aria-live makes screen readers announce the number when it changes. --> <p class="counter-value" id="count" aria-live="polite">0</p>
CSSthe look
- 03
Style states with classes
JavaScript and CSS each do one job here. JavaScript decides the state, like "the count is above zero", and adds or removes a class such as
is-positive. CSS decides what that class looks like. To change the colours later, you only edit the CSS./* The colour is set by JavaScript adding one of these classes. */ .counter-value.is-positive { color: #15803d; } .counter-value.is-negative { color: #b91c1c; } - 04
Keep numbers steady with
tabular-numsIn most fonts, digits have different widths: a 1 is narrower than an 8. So when the number changes from 11 to 18, the text shifts sideways.
font-variant-numeric: tabular-numsgives every digit the same width, so it stays put./* Every digit takes the same width, so the number doesn't jiggle. */ font-variant-numeric: tabular-nums; - 05
Show focus for keyboard users
Focus is the browser's way of tracking which element the keyboard will act on.
:focus-visibledraws an outline when someone moves to a button with Tab, but not when they click it with a mouse. Keyboard users always see where they are, and mouse users don't get a ring after every click./* A visible outline for keyboard users only. */ .counter-button:focus-visible { outline: 3px solid #2563eb; outline-offset: 3px; }
JavaScriptthe behaviour
- 06
Find elements with
querySelectorJavaScript can't change something on the page until it has a reference to it.
document.querySelector()takes a CSS selector, like#count, and returns the first element that matches. Finding each element once at the top, and storing it in aconst, means the rest of the code can reuse it.// Grab the elements we need once, at the start. const countDisplay = document.querySelector("#count"); const decreaseButton = document.querySelector("#decrease"); const resetButton = document.querySelector("#reset"); const increaseButton = document.querySelector("#increase"); - 07
Keep changing data in a variable
A variable is a named place to store a value, so your code can remember it. The number lives in
count, and the page only shows a copy of it. JavaScript has two common ways to make a variable:constfor a value you never replace, andletfor one you do. The count goes up and down with every click, so it'slet.// The count lives in a variable; `let` because it changes. let count = 0; - 08
Put "update the page" in one function
Every button needs the page to show the new count, so that code lives in one function,
updateCountDisplay(), and each button calls it. If you change how the number looks, you change it in one place. Inside,classList.toggle()adds a class when its condition is true and removes it when false, which picks the colour.// One function updates the page, so every button stays in sync. function updateCountDisplay() { countDisplay.textContent = count; // A condition picks the colour: green above zero, red below. countDisplay.classList.toggle("is-positive", count > 0); countDisplay.classList.toggle("is-negative", count < 0); } - 09
React to clicks with
addEventListeneraddEventListener("click", ...)tells the browser: when this button is clicked, run this function. Each function does two things in order. It changescountfirst, then callsupdateCountDisplay(). Data first, then the page: if the page ever looks wrong, check the variable.// Each button changes the count, then calls updateCountDisplay() to show it. decreaseButton.addEventListener("click", () => { count = count - 1; updateCountDisplay(); }); resetButton.addEventListener("click", () => { count = 0; updateCountDisplay(); }); increaseButton.addEventListener("click", () => { count = count + 1; updateCountDisplay(); });
03
Wrap up
Track your progress, then go deeper or keep going.
Take it further
Once it works, try these on your own. Each one uses what you've just learned, plus one new idea.
- Challenge 01
Stop the count going below zero, and disable Decrease when it gets there.
- Challenge 02
Add a step input, so each click adds 5 or 10 instead of 1.
- Challenge 03
Save the count in localStorage so it's still there after a reload.
Finished Counter?
Mark it done to track your progress.
Go deeper
The full article explains each idea in more depth, why the code is written this way, and more than fits on this page.
Article coming soon. See the series so far (opens in a new tab)