Character Counter
Build a live character counter that updates as the user types.
Character Counter
Problem
Build a character counter that updates live as the user types inside a textarea. The counter should warn the user as they approach the limit and turn red when they exceed it.
User types: "Hello"
Output: 5 / 150
User types: "Hello World"
Output: 11 / 150
User types 131+ characters
Output: 131 / 150 — counter turns orange (warning)
User types 151+ characters
Output: 151 / 150 — Limit exceeded! — counter turns redLogic
- Select the textarea and counter elements from the DOM
- Listen for the
inputevent on the textarea — fires on every keystroke - Read
textarea.value.lengthto get the current character count - Update the counter's text with current count and max limit
- Remove any existing state classes before applying new ones
- Apply
warningclass if count is within 20 of the limit - Apply
dangerclass if count exceeds the limit
Flow
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Character Counter</title>
<style>
body {
font-family: sans-serif;
max-width: 500px;
margin: 60px auto;
padding: 0 20px;
}
textarea {
width: 100%;
height: 150px;
padding: 12px;
font-size: 1rem;
border: 2px solid #ccc;
border-radius: 8px;
resize: vertical;
box-sizing: border-box;
}
/* default counter style */
#counter {
margin-top: 8px;
font-size: 0.9rem;
color: #555;
}
/* close to limit */
#counter.warning {
color: orange;
font-weight: 500;
}
/* over the limit */
#counter.danger {
color: red;
font-weight: 600;
}
</style>
</head>
<body>
<h1>Character Counter</h1>
<!-- textarea the user types in -->
<textarea id="text-input" placeholder="Start typing..."></textarea>
<!-- live counter display -->
<p id="counter">0 / 150</p>
<script src="script.js"></script>
</body>
</html>Solution 1 — classList toggle approach
// Step 1 — select elements from the DOM
const textarea = document.querySelector("#text-input"); // the textarea user types in
const counter = document.querySelector("#counter"); // the paragraph showing the count
// Step 2 — define constants
const MAX_LIMIT = 150; // maximum characters allowed
const WARNING_THRESHOLD = 130; // start warning 20 chars before limit
// Step 3 — attach input event listener
// 'input' fires on every change — typing, deleting, pasting
textarea.addEventListener("input", updateCounter);
// Step 4 — define the update function
function updateCounter() {
// read how many characters are currently in the textarea
const currentLength = textarea.value.length;
// update the counter display
counter.textContent = `${currentLength} / ${MAX_LIMIT}`;
// Step 5 — reset classes before applying new ones
// this ensures we never have both warning and danger at the same time
counter.classList.remove("warning", "danger");
// Step 6 — apply correct class based on count
if (currentLength > MAX_LIMIT) {
counter.classList.add("danger");
counter.textContent = `${currentLength} / ${MAX_LIMIT} — Limit exceeded!`;
} else if (currentLength >= WARNING_THRESHOLD) {
counter.classList.add("warning");
}
}Code Execution — Solution 1
Trace through typing "Hello" (5 characters):
| Step | Code | Result |
|---|---|---|
| Event fires | input on textarea | — |
| Read length | textarea.value.length | 5 |
| Update text | counter.textContent | "5 / 150" |
| Remove classes | classList.remove("warning", "danger") | no classes |
Check 5 > 150 | false | skip danger |
Check 5 >= 130 | false | skip warning |
| Final state | counter | "5 / 150" grey |
Trace through typing 135 characters:
| Step | Code | Result |
|---|---|---|
| Read length | textarea.value.length | 135 |
| Update text | counter.textContent | "135 / 150" |
| Remove classes | classList.remove(...) | no classes |
Check 135 > 150 | false | skip danger |
Check 135 >= 130 | true | add warning |
| Final state | counter | "135 / 150" orange |
Trace through typing 155 characters:
| Step | Code | Result |
|---|---|---|
| Read length | textarea.value.length | 155 |
| Remove classes | classList.remove(...) | no classes |
Check 155 > 150 | true | add danger |
| Update text | counter.textContent | "155 / 150 — Limit exceeded!" |
| Final state | counter | red with message |
Solution 2 — using a data attribute for max limit
Instead of hardcoding the limit in JavaScript, read it from the HTML using a data-max attribute. This makes the component reusable — change the limit in HTML without touching JS.
<!-- max limit set directly in HTML -->
<textarea id="text-input" data-max="150" placeholder="Start typing..."></textarea>
<p id="counter">0 / 150</p>const textarea = document.querySelector("#text-input");
const counter = document.querySelector("#counter");
// read the max limit from the HTML data attribute
// Number() converts the string "150" to the number 150
const MAX_LIMIT = Number(textarea.dataset.max);
const WARNING_THRESHOLD = MAX_LIMIT - 20;
textarea.addEventListener("input", () => {
const currentLength = textarea.value.length;
const remaining = MAX_LIMIT - currentLength; // how many chars left
// show remaining instead of current count
counter.textContent = `${currentLength} / ${MAX_LIMIT} (${remaining} remaining)`;
counter.classList.remove("warning", "danger");
if (currentLength > MAX_LIMIT) {
counter.classList.add("danger");
counter.textContent = `${currentLength} / ${MAX_LIMIT} — ${Math.abs(remaining)} over limit!`;
} else if (currentLength >= WARNING_THRESHOLD) {
counter.classList.add("warning");
}
});Code Execution — Solution 2
Trace through typing 148 characters with data-max="150":
| Step | Code | Result |
|---|---|---|
| Read data attribute | textarea.dataset.max | "150" (string) |
| Convert to number | Number("150") | 150 |
| Read length | textarea.value.length | 148 |
| Calculate remaining | 150 - 148 | 2 |
| Update text | counter | "148 / 150 (2 remaining)" |
Check 148 > 150 | false | skip danger |
Check 148 >= 130 | true | add warning |
Trace through typing 153 characters:
| Step | Code | Result |
|---|---|---|
| Read length | textarea.value.length | 153 |
| Calculate remaining | 150 - 153 | -3 |
Check 153 > 150 | true | add danger |
Math.abs(-3) | 3 | 3 |
| Update text | counter | "153 / 150 — 3 over limit!" |
Solution 3 — preventing input beyond the limit
Instead of just warning the user, block typing after the limit is reached using maxlength in HTML plus JS for the live counter.
<!-- maxlength stops input at 150 automatically -->
<textarea id="text-input" maxlength="150" placeholder="Start typing..."></textarea>
<p id="counter">0 / 150</p>const textarea = document.querySelector("#text-input");
const counter = document.querySelector("#counter");
const MAX_LIMIT = 150;
textarea.addEventListener("input", () => {
const currentLength = textarea.value.length;
const remaining = MAX_LIMIT - currentLength;
counter.textContent = `${currentLength} / ${MAX_LIMIT}`;
counter.classList.remove("warning", "danger");
if (remaining === 0) {
// exactly at the limit
counter.classList.add("danger");
counter.textContent = `${MAX_LIMIT} / ${MAX_LIMIT} — No characters remaining`;
} else if (remaining <= 20) {
counter.classList.add("warning");
counter.textContent = `${currentLength} / ${MAX_LIMIT} — ${remaining} left`;
}
});maxlength on a textarea is a built-in HTML attribute that prevents the user from typing beyond the set limit. Combining it with JavaScript gives you both hard prevention and live visual feedback.
Which Solution to Use
| Solution | How | Best when |
|---|---|---|
| Solution 1 | classList toggle, hardcoded limit | Simple, quick to set up |
| Solution 2 | data-max attribute, remaining count | Reusable across multiple textareas |
| Solution 3 | maxlength + JS counter | Hard limit needed, no override allowed |
Output
User types "Hello" → 5 / 150 (grey)
User types 130 chars → 130 / 150 (orange warning)
User types 151 chars → 151 / 150 — Limit exceeded! (red)