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.
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
| Attribute | Value | Purpose |
|---|---|---|
for | Textarea ID | Connects the plugin root to its sibling textarea. |
data-min / data-max | Non-negative integer | Defines minimum and maximum character guidance. |
data-warning-at | Non-negative integer | Sets the exact count where warning state begins. |
data-warning-percent | Integer; default 80 | Sets the warning threshold as a percentage of data-max. |
data-hard-limit | Boolean | Adds maxlength when the textarea does not already define it. |
data-show-remaining | Boolean | Shows the number of characters remaining. |
data-live | off, polite, or assertive | Controls the generated status announcement; default is polite. |
data-message-* | Text template | Customizes 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-describedbywithout 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.