data-count

Live character or word count for textareas with configurable limits. Color shifts at warning and error thresholds for visual feedback.

Overview

The data-count attribute adds a live counter below a textarea that updates as the user types. It supports both character counting (with maxlength) and word counting (with data-maxwords), with automatic color shifts at warning and error thresholds.

<form-field>\n <label for="bio">Bio</label>\n <textarea id="bio" maxlength="200" data-count rows="3"></textarea>\n</form-field>

How It Works

Add data-count to any <textarea>. The init script:

  1. Creates an <output> element with class="textarea-counter"
  2. Sets aria-live="polite" and aria-atomic="true" on the counter for screen reader announcements
  3. Places the counter after the textarea (inside <form-field> if present, otherwise directly after)
  4. Listens for input events to update the count in real time
  5. Calculates thresholds: warning at ≥80% of limit, error at ≥100%
  6. Sets counter.dataset.state to "", "warning", or "error" for CSS styling
  7. Sets data-count-init to prevent double-binding

Attributes

Attribute Values Description
data-count "", "words" Enables the counter. Empty string (default) counts characters. "words" counts words.
maxlength number Standard HTML attribute. Used as the character limit. The counter displays current / max format.
data-maxwords number Word limit for word-count mode. The counter displays current / max format.
data-count-init boolean Set automatically to prevent double-binding. Do not set manually.

Character Count

The default mode counts characters. Pair with maxlength to show a current / max display and enforce the limit.

<form-field>\n <label for="bio">Bio</label>\n <textarea id="bio" maxlength="200" data-count rows="3"></textarea>\n</form-field>

Word Count

Set data-count="words" for word counting. Use data-maxwords to set a word limit. Words are split on whitespace.

<form-field>\n <label for="essay">Essay</label>\n <textarea id="essay" data-count="words" data-maxwords="50" rows="4"></textarea>\n</form-field>

Count Without Limit

Omit maxlength or data-maxwords to show a running count without enforcing a limit. No threshold states are applied.

Characters without limit

<form-field>\n <label for="notes">Notes</label>\n <textarea id="notes" data-count rows="3"></textarea>\n</form-field>

Words without limit

<form-field>\n <label for="freeform">Freeform</label>\n <textarea id="freeform" data-count="words" rows="3"></textarea>\n</form-field>

Display Format

The counter text adapts based on the configuration:

Configuration Display Example
Characters with maxlength current / max 42 / 200
Characters without limit count character(s) 42 characters
Words with data-maxwords current / max 12 / 50
Words without limit count word(s) 12 words

Threshold States

When a limit is set, the counter shifts color at configurable thresholds via data-state:

  • Normal (data-state="") — below 80% of the limit, uses muted text color
  • Warning (data-state="warning") — at or above 80% of the limit, shifts to warning color
  • Error (data-state="error") — at or above 100% of the limit, shifts to danger color with bold weight

Without a limit, no threshold states are applied and the counter stays in its default style.

Combined with data-grow

Use data-count alongside data-grow for a textarea that expands as the user types while showing the count.

<form-field>\n <label for="msg">Message</label>\n <textarea id="msg" data-count data-grow maxlength="500" rows="2"></textarea>\n</form-field>

Programmatic Access

The counter element is a standard DOM node. Query it to read the current state programmatically.

const textarea = document.querySelector('[data-count]'); textarea.addEventListener('input', () => { const counter = textarea.parentElement.querySelector('.textarea-counter'); console.log('Count state:', counter.dataset.state); console.log('Counter text:', counter.textContent); });

Styling

The counter is an <output> element styled via the .textarea-counter class. Threshold states are driven by data-state attribute selectors.

/* Counter element */ .textarea-counter { font-size: var(--text-sm); color: var(--color-text-muted); text-align: right; margin-block-start: var(--size-2xs); } /* Warning state (>=80% of limit) */ .textarea-counter[data-state="warning"] { color: var(--color-warning); } /* Error state (>=100% of limit) */ .textarea-counter[data-state="error"] { color: var(--color-danger); font-weight: 600; }

The warning state uses --color-warning and the error state uses --color-danger with increased font weight for emphasis.

Dynamic Elements

Textareas added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.

// Dynamically added textareas are auto-enhanced via MutationObserver const textarea = document.createElement('textarea'); textarea.maxLength = 140; textarea.dataset.count = ''; textarea.rows = 3; document.querySelector('form-field').appendChild(textarea); // counter element is created automatically — no manual init needed

Accessibility

  • The counter uses aria-live="polite" so screen readers announce count changes without interrupting the user
  • aria-atomic="true" ensures the entire counter text is read, not just the changed portion
  • The <output> element has implicit ARIA semantics for live regions
  • Threshold state changes (warning, error) are conveyed through the updated counter text
  • The counter is placed after the textarea in DOM order, matching visual reading order
  • Without JavaScript, the textarea works normally with no counter (progressive enhancement)