DocsHub
JavascriptBeginner

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 red

Logic

  1. Select the textarea and counter elements from the DOM
  2. Listen for the input event on the textarea — fires on every keystroke
  3. Read textarea.value.length to get the current character count
  4. Update the counter's text with current count and max limit
  5. Remove any existing state classes before applying new ones
  6. Apply warning class if count is within 20 of the limit
  7. Apply danger class if count exceeds the limit

Flow

yes no yes no User types input event fires Read textarea.value.length Update counter text Remove warning and danger classes count > 150? Add danger classShow exceeded message count >= 130? Add warning class Keep default style

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):

StepCodeResult
Event firesinput on textarea
Read lengthtextarea.value.length5
Update textcounter.textContent"5 / 150"
Remove classesclassList.remove("warning", "danger")no classes
Check 5 > 150falseskip danger
Check 5 >= 130falseskip warning
Final statecounter"5 / 150" grey

Trace through typing 135 characters:

StepCodeResult
Read lengthtextarea.value.length135
Update textcounter.textContent"135 / 150"
Remove classesclassList.remove(...)no classes
Check 135 > 150falseskip danger
Check 135 >= 130trueadd warning
Final statecounter"135 / 150" orange

Trace through typing 155 characters:

StepCodeResult
Read lengthtextarea.value.length155
Remove classesclassList.remove(...)no classes
Check 155 > 150trueadd danger
Update textcounter.textContent"155 / 150 — Limit exceeded!"
Final statecounterred 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":

StepCodeResult
Read data attributetextarea.dataset.max"150" (string)
Convert to numberNumber("150")150
Read lengthtextarea.value.length148
Calculate remaining150 - 1482
Update textcounter"148 / 150 (2 remaining)"
Check 148 > 150falseskip danger
Check 148 >= 130trueadd warning

Trace through typing 153 characters:

StepCodeResult
Read lengthtextarea.value.length153
Calculate remaining150 - 153-3
Check 153 > 150trueadd danger
Math.abs(-3)33
Update textcounter"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

SolutionHowBest when
Solution 1classList toggle, hardcoded limitSimple, quick to set up
Solution 2data-max attribute, remaining countReusable across multiple textareas
Solution 3maxlength + JS counterHard 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)

On this page