Eric Niquette

Introduction

When faced with inputs that have a limited number of characters allowed, I found it was really difficult to estimate what 200 or 500 meant, let alone trying to track it when using a screen reader.

The following script takes the guesswork out of it. It dynamically counts and displays the number of characters remaining based on the input's maxlength value. The script also provides a secondary counter for screen readers.

Demo

As you type in the following field, notice how the first counter updates dynamically as you type whereas the second only updates after two seconds of inactivity. The second counter is normally hidden visually but is left on for this demo.

See the Pen Dynamic and accessible input character counter using JavaScript by Eric Niquette (@ericniquette) on CodePen.

How it works

The script binds itself to any input with the .counted class and looks for the maxlength attribute. It uses that value to populate the next .screen-only and .sr-only elements. This is repeated for every individual input.

As the user types in the field, the number of characters remaining is counted based on the maxlegnth and this value is dynamically appended to the .screen-only element. After two second of inactivity, the .sr-only element is updated.

The inactivity timer takes all events into consideration and does not only track keyboard input. Pasting text, for example, will reset the timer.

Solution

HTML

The script relies on elements with specific class names and attributes: an input or textarea with the .counted class and a maxlength attribute. It also requires two elements, like a paragraph, with the .screen-only and .sr-only classes that the script can replace.

The rest of the HTML is a label for our input and a few supporting ARIA attributes for screen readers.

If you want to have multiple instances or inputs, update the "1" labels and ID values.

<label for="input_name1">Please enter text (Required)</label>
<textarea id="input_name1" maxlength="1000" class="counted" aria-describedby="counter_description1"></textarea>

<p class="counter screen-only" aria-hidden="true">Enter up to 1000 characters</p>
<p class="counter sr-only" id="counter_description1">Enter up to 1000 characters</p>

CSS

The only CSS needed is a class to visually hide the second counter. This is my preferred method but feel free to use whichever technique you're most comfortable with.

.sr-only {
  position: absolute;
  left: -9999em;
  opacity: 0;
  overflow: hidden;
  top: -9999em;
  }

JavaScript

The script is relatively straightforward. The delay is, by default, set to 2000 milliseconds can be tweaked to suit your needs.

document.addEventListener("DOMContentLoaded", function () {

  var timer = '';
  var counted = document.querySelectorAll('.counted');
    counted.forEach(function (elem) {

      elem.addEventListener("input", function () {

        clearTimeout(timer);
        var pause = this;
        var limit = this.getAttribute("maxlength");
        var remainingChars = limit - this.value.length;
          if (remainingChars <= 0) {
            this.value = this.value.substring(0, limit);
          }
        var screenOnlyElem = this.nextElementSibling;
          while (screenOnlyElem && screenOnlyElem.classList.contains('screen-only') === false) {
            screenOnlyElem = screenOnlyElem.nextElementSibling;
            }

          if (screenOnlyElem) {
            screenOnlyElem.textContent = remainingChars <= -1 ? 0 : remainingChars + ' character(s) remaining';
          }

      timer = setTimeout(function () {
        var srOnlyElem = pause.nextElementSibling;
        while (srOnlyElem && srOnlyElem.classList.contains('sr-only') === false) {
        srOnlyElem = srOnlyElem.nextElementSibling;
        }

        if (srOnlyElem) {
          srOnlyElem.textContent = remainingChars <= -1 ? 0 : remainingChars + ' character(s) remaining';
        }
    }, 2000);
  });

    elem.dispatchEvent(new Event('input'));
  });
});