skip to content

JavaScript: Counting words in a text area

When collecting input from the use it's often necessary to limit the amount of text entered into a text field. For normal text inputs we have the maxlength attribute, but that is a bit crude and doesn't apply to textareas.

Counting characters as you type

Here is a simple demonstration of a text input when you can enter any amount of text. The character and word counts are displayed, but no limits are enforced:

Word counter TEXTAREA

What we're looking for is a solution that will let us limit the text input either by the number of characters or by the number of words, and to have some kind of feedback presented to the user as they approach the limit.

All of the examples on this page use the exact same JavaScript code just with different settings. The source code can be found below.

Limiting number of characters

In this form the input is limited to 100 characters, which includes spaces, and you will be prevented from typing once the limit is reached:

Length-limited TEXTAREA

Limiting number of words

Here we are instead limiting the input to 20 words, where a word is any series of characters surrounded by whitespace:

Word-limited TEXTAREA

Limiting both characters and words

Finally, in the form below there are two limits imposed - the number of characters and the number of words and both will be limited:

Length- and word-limited TEXTAREA

Source code

The script below included on the page is all that you need to implement any or all of the above examples:

<script> // Original JavaScript code by Chirp Internet: // Please acknowledge use of this code by including this header. const charCount = (input) => input.value.length; const wordCount = (input) => { if(input.value) { return input.value.trim().split(/\s+/).length; } else { return 0; } }; const showCharCount = (input) => { let count = charCount(input); let output = count; if("charlimit" in input.dataset) { output += "/" + input.dataset.charlimit + " characters"; } else if(1 == count) { output += " character"; } else { output += " characters"; } input.parentNode.querySelector("span.chars").innerText = output; }; const showWordCount = (input) => { let count = wordCount(input); let output = count; if("wordlimit" in input.dataset) { output += "/" + input.dataset.wordlimit + " words"; } else if(1 == count) { output += " word"; } else { output += " words"; } input.parentNode.querySelector("span.words").innerText = output; }; const checkLimits = (input) => { if("wordlimit" in input.dataset) && (wordCount(input) > input.dataset.wordlimit)) { input.setCustomValidity("Please limit your input to " + input.dataset.wordlimit + " words."); } else if("charlimit" in input.dataset && (charCount(input) > input.dataset.charlimit)) { input.setCustomValidity("Please limit your input to " + input.dataset.charlimit + " characters."); } else { input.setCustomValidity(""); } }; document.querySelectorAll("textarea").forEach((input) => { /* for each textarea on the current page */ let hasLimits = false; if("wordcount" in input.dataset) { let el = document.createElement("span"); el.className = "words"; input.insertAdjacentElement("afterend", el); input.addEventListener("keydown", (e) => { showWordCount(input); } ); input.addEventListener("keyup", (e) => { showWordCount(input); } ); showWordCount(input); } if("wordlimit" in input.dataset) { hasLimits = true; input.addEventListener("keypress", (e) => { if(wordCount(input) >= input.dataset.wordlimit) { if(e.key.match(/\s/)) { e.preventDefault(); } } }); } if("charcount" in input.dataset) { let el = document.createElement("span"); el.className = "chars"; input.insertAdjacentElement("afterend", el); input.addEventListener("keydown", (e) => { showCharCount(input); } ); input.addEventListener("keyup", (e) => { showCharCount(input); } ); showCharCount(input); } if("charlimit" in input.dataset) { hasLimits = true; input.addEventListener("keypress", (e) => { if(charCount(input) >= input.dataset.charlimit) { e.preventDefault(); } }); } if(hasLimits) { /* enforce limits client-side using HTML5 validation triggers */ input.addEventListener("change", (e) => { checkLimits(; }); } }); </script>

expand code box

You will see that we are making use of custom data elements to identify which textareas on the page need to have counters and limiters:

display a character counter
display a word counter
limit input to N characters
limit input to N words

So all you need to do is mark up your textarea inputs with the relevant custom data elements and the code will do the rest. See below for an example.

For each textarea with a limit we have added an onchange event which assigns an HTML5 custom error message to the input. This will prevent the containing form from being submitted in most browsers, but you should also always validate the data server-side.

HTML5 form validation

Clicking the button below will run a validity check using form.reportValidity(). This process is also invoked when you try to submit a form that uses HTML5 validation.

If any of the input values are out of bounds an HTML5 error message will appear. Here is a screenshot of how that might look:

Sample HTML Markup

Here is some example HTML code for setting up a textarea input with a word counter and a limit on the number of words:

<style> textarea ~ span { display: block; font-size: small; color: #666; } </style> <form method="POST" action="#" accept-charset="UTF-8"> <p><textarea name="sometext" data-wordcount="true" data-wordlimit="20" rows="2"></textarea></p> <p><input type="submit"></p> </form>

Don't forget to include the JavaScript!

Handling copy paste

We haven't addressed this directly as there are too many variables involving the cursor position, any current selection, and the text to be pasted.

The best solution is probably just to accept the pasted text and then truncate the contents of the textarea using either JavaScript or a back-end script.

< JavaScript

User Comments

Post your comment or question

2 February, 2023

This is great! However, I can copy and paste past the limits...
One could programmatically truncate the text that is transmitted, or ignore anything over the limit...

The problems is that 'paste' doesn't trigger our event handlers, but if you put this into a FORM you will see that an HTML5 validation message will appear if there is too much text when you try to submit. I'll be adding that to the page.