Create a counter in JavaScript

Muhammad Saqib Ilyas
7 min readJun 21, 2024

--

Let’s create a web-based counter that can count up or down and be reset. A count is displayed on the web page, which is initially 0. There’s a button that increments the count, another button that decrements the count, and another button that resets the count to 0. Here’s what our application will look like when it is loaded.

The application look when it initially loads

After a few clicks of the “Increase” button, it may look something like:

The application after a few clicks of the “Increase” button

Let’s start with the following HTML file named index.html:

<!DOCTYPE html>
<html>
<head>
<title>Counter</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Counter</h1>
<div id="count">0</div>
<div id="buttons">
<div class="button" id="decrease">Decrease</div>
<div class="button" id="reset">Reset</div>
<div class="button" id="increase">Increase</div>
</div>
</main>
</body>
</html>

We have an h1 element that displays the application’s name and purpose. We have three div elements for the up, down, and reset buttons. We wrap these into another div element so that we can control their positioning collectively. We also link to a CSS file and a JavaScript file.

Let’s create that CSS file named style.css in the same directory as the index.html file.

*, ::before, ::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}

We start with a CSS reset that gets rid of any browser-specific margin and padding properties, and indicates that border widths should be included when calculating box sizes.

Let’s make that h1 element horizontally centred, and put a bit of space above it.

h1 {
text-align: center;
margin-top: 10px;
}

Let’s now style the count itself.

#count {
font-size: 28px;
text-align: center;
margin: 20px 0;
}

We target the count with an ID selector. We set the font size to 28 pixels. We center the count horizontally within the div element. We also add a bit of margin above and below the div element.

Let’s position and style the buttons, now.

#buttons {
margin: 10px auto;
width: 350px;
display: flex;
justify-content: space-between;
}

.button {
display: flex;
width: 100px;
height: 30px;
justify-content: center;
align-items: center;
border: 2px solid black;
border-radius: 15px;
cursor: pointer;
}

To put the buttons in a row neatly centered on the screen, we use CSS Flexbox on the div element with an ID of buttons. We give that div element a width of 350 pixels, which is slightly greater than the width of the three buttons combined. To spread the space between the three buttons, we set the justify-content property to space-between. We target the three buttons using a class selector. We give each button a width of 100 pixels and a height of 30 pixels. To center the text both horizontally and vertically within the button, we set justify-content and align-items both to center. We give the buttons a border and rounded corners. We set the cursor property to pointer so that hovering over a button gives the user a visual cue that this is an active element.

Let’s now implement the counting functionality. We start with the increment functionality. We create a file named app.js in the same directory as the index.html file.

let count = 0;
const inc = document.getElementById('increase')
const countDisplay = document.getElementById('count')

inc.addEventListener('click', increment)

function increment() {
count = count + 1
countDisplay.innerText = count
}

We declare a variable named count to hold the current value of count. We initialize it to 0. Note that we declare it with the let keyword rather than const, so that we can update its value in response to the button clicks. We acquire objects referencing the “Increase” button using the getElementById() method because we want to hook up a click event listener to it. We also want to display the updated count in the corresponding div element. So, we acquire an object refering that as well. We use the addEventListener() method to hook up a click event listener. We mention the name of the event listener as increment. We declare that function. All it does is increase the value of count by 1, and update the displayed value of count. Now, when you click the “Increase” button, the count increases by one.

One problem that you’d notice is that sometimes when you click on a button, the button text gets selected. To avoid that, we can use the user-select CSS property and its vendor-specific prefixes.

.button {
/* Other styles */
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}

Now, we can implement the functionality of the remaining two buttons.

let count = 0;
const inc = document.getElementById('increase')
const dec = document.getElementById('decrease')
const res = document.getElementById('reset')
const countDisplay = document.getElementById('count')

inc.addEventListener('click', increment)
dec.addEventListener('click', decrement)
res.addEventListener('click', reset)

function increment() {
count = count + 1
updateDisplay()
}

function decrement() {
count = count - 1
updateDisplay()
}

function reset() {
count = 0
updateDisplay()
}

function updateDisplay() {
countDisplay.innerText = count
}

The noteworthy part is that we take the countDisplay.innerText = count statement into a separate function and called it from within each of the event listeners. If we don’t do that, we’d be violating the Don’t Reapeat Yourself (DRY) principle. The down side that we are avoiding is that if you need to make any change to the display update logic, you have to make changes in three different places. In the present form, we’d only need to make changes in one place.

Now, all three functions work, but one problem is that the count can go below 0, which does not sound like a good idea. Let’s fix that. First, let’s declare a CSS class for a disabled button.

.disabled {
color: #777;
border-color: #777;
cursor: auto;
}

This one has a dim text color and border. It also has the default cursor. So, visually, it informs the user that the button is disabled. When the application loads, there’s no point decrementing or resetting the count. So, we can apply this class to these functions in HTML:

<!DOCTYPE html>
<html>
<head>
<title>Counter</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Counter</h1>
<div id="count">0</div>
<div id="buttons">
<div class="button disabled" id="decrease">Decrease</div>
<div class="button disabled" id="reset">Reset</div>
<div class="button" id="increase">Increase</div>
</div>
</main>
<script src = "app.js"></script>
</body>
</html>

The above changes are only visual. These don’t prevent the event listeners from being invoked. We can remove the calls to the addEventListener() method for the “Decrement” and “Reset” buttons from the JavaScript code.

let count = 0;
const inc = document.getElementById('increase')
const dec = document.getElementById('decrease')
const res = document.getElementById('reset')
const countDisplay = document.getElementById('count')

inc.addEventListener('click', increment)

function increment() {
count = count + 1
updateDisplay()
}

function decrement() {
count = count - 1
updateDisplay()
}

function reset() {
count = 0
updateDisplay()
}

function updateDisplay() {
countDisplay.innerText = count
}

But this means that we can’t ever decrement or reset the count. We need to enable the “Decrement” and “Reset” buttons when the count is greater than zero. That happens in the increment() function, so let’s modify that function.

function enableButtons() {
dec.addEventListener('click', decrement)
res.addEventListener('click', reset)
dec.classList.remove('disabled')
res.classList.remove('disabled')
}

function increment() {
count = count + 1
enableButtons()
updateDisplay()
}

From the increment() function, we call an enableButtons() function that adds the click event listeners to the “Decrease” and “Reset” buttons, and removes the disabled class from them. Now, the two buttons are enabled and clickable only after you increment the count. However, the two buttons should be disabled once you click the “Reset” button or “Decrease” button so that the count becomes 0.

function disableButtons() {
dec.removeEventListener('click', decrement)
res.removeEventListener('click', reset)
dec.classList.add('disabled')
res.classList.add('disabled')
}

function decrement() {
count = count - 1
if (count === 0) {
disableButtons()
}
updateDisplay()
}

function reset() {
count = 0
disableButtons()
updateDisplay()
}

We declare a function named disableButtons(). We call it from the reset() function. We also call it from the decrement() function if the count drops to 0.

That’s all folks!

That completes our project. I hope you enjoyed building it. Can you replace the buttons with some icons such as the ones from Google Fonts? Can you implement some sort of persistence so that if you close the browser tab, and open the application again, the previous value of count is still displayed?

You may get the code from this GitHub repository. If you want to copy-paste, here’s our HTML code.

<!DOCTYPE html>
<html>
<head>
<title>Counter</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Counter</h1>
<div id="count">0</div>
<div id="buttons">
<div class="button disabled" id="decrease">Decrease</div>
<div class="button disabled" id="reset">Reset</div>
<div class="button" id="increase">Increase</div>
</div>
</main>
<script src = "app.js"></script>
</body>
</html>

Here’s the CSS.

*, ::before, ::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}

h1 {
text-align: center;
margin-top: 10px;
}

#count {
font-size: 28px;
text-align: center;
margin: 20px 0;
}

#buttons {
margin: 10px auto;
width: 350px;
display: flex;
/* gap: 10px; */
justify-content: space-between;
}

.button {
display: flex;
width: 100px;
height: 30px;
justify-content: center;
align-items: center;
border: 2px solid black;
border-radius: 15px;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}

.disabled {
color: #777;
border-color: #777;
cursor: auto;
}

Here’s the JavaScript.

let count = 0;
const inc = document.getElementById('increase')
const dec = document.getElementById('decrease')
const res = document.getElementById('reset')
const countDisplay = document.getElementById('count')

inc.addEventListener('click', increment)

function enableButtons() {
dec.addEventListener('click', decrement)
res.addEventListener('click', reset)
dec.classList.remove('disabled')
res.classList.remove('disabled')
}

function disableButtons() {
dec.removeEventListener('click', decrement)
res.removeEventListener('click', reset)
dec.classList.add('disabled')
res.classList.add('disabled')
}

function increment() {
count = count + 1
enableButtons()
updateDisplay()
}

function decrement() {
count = count - 1
if (count === 0) {
disableButtons()
}
updateDisplay()
}

function reset() {
count = 0
disableButtons()
updateDisplay()
}

function updateDisplay() {
countDisplay.innerText = count
}

--

--

Muhammad Saqib Ilyas

A computer science teacher by profession. I love teaching and learning programming. I like to write about frontend development, and coding interview preparation