Package
a11y-character-count
Built with
TypeScript and native HTML
Dependencies
None
Best for
Textarea limits and guidance

Live demo

Write a concise support request

Enter a useful message between 10 and 120 characters. Type past 96 characters to see a warning, exceed 120 to see an invalid state, then reset the specimen.

Briefly describe what happened and what you expected.

Important: the textarea and plugin <output> are siblings. The plugin does not wrap the textarea.

02 / Start

Install and register

Terminal

npm install a11y-character-count

JavaScript

import { initCharacterCounts } from "a11y-character-count";

initCharacterCounts();

Initialize the declarative plugin roots after their markup is available.

03 / Compose

Copyable markup

<label for="message">Message</label>
<textarea id="message" name="message"></textarea>
<output
  class="a11y-character-count"
  data-a11y-character-count
  for="message"
  data-min="10"
  data-max="120"
  data-warning-percent="80"
  data-show-remaining
></output>

The for value must match the id of a native <textarea> in the same document.

04 / Configure

Attributes and states

AttributeValuePurpose
forTextarea IDConnects the plugin root to its sibling textarea.
data-min / data-maxNon-negative integerDefines minimum and maximum character guidance.
data-warning-atNon-negative integerSets the exact count where warning state begins.
data-warning-percentInteger; default 80Sets the warning threshold as a percentage of data-max.
data-hard-limitBooleanAdds maxlength when the textarea does not already define it.
data-show-remainingBooleanShows the number of characters remaining.
data-liveoff, polite, or assertiveControls the generated status announcement; default is polite.
data-message-*Text templateCustomizes empty, warning, too-short, and too-long messages.

The plugin exposes data-state as empty, valid, warning, too-short, too-long, disabled, or readonly.

Methods and events

Use update(), validate(), getCount(), and destroy() on an instance created with createCharacterCount(). Lifecycle events bubble from the plugin root:

init, update, valid, invalid, warning, limit-exceeded, error, and destroy, each prefixed with a11y-character-count:.

05 / Behavior

Accessibility behavior

  • The native textarea keeps its label, focus behavior, and keyboard editing model.
  • The visible counter and message are appended to aria-describedby without removing existing description IDs.
  • A separate live region briefly exposes concise warning, invalid, recovery, and reset transitions instead of announcing every keystroke.
  • aria-invalid="true" is managed only for too-short and too-long states, while application-owned values are preserved.
  • The form reset event refreshes the count after the textarea returns to its default value.
  • Missing or non-textarea targets emit an error event and otherwise fail safely.

Keyboard and screen reader expectations

No custom keyboard commands are introduced. Use Tab to reach the textarea and type with normal editing keys. The visible count and message remain available as descriptions, while meaningful state transitions are briefly exposed through a live region and then cleared; test the chosen data-live setting with the assistive technologies supported by your product.

06 / Extend

Styling hooks and limitations

Style the root with [data-a11y-character-count] and state selectors such as [data-a11y-character-count][data-state="warning"]. Generated elements use .a11y-character-count__counter, .a11y-character-count__message, and .a11y-character-count__announcement.

  • The plugin counts JavaScript string length; it does not segment user-perceived grapheme clusters.
  • It targets native textareas only and does not create, wrap, or replace the form control.
  • Client-side feedback does not replace server-side validation.
  • Invalid or missing target references are ignored safely but cannot display a useful count.