Text Area With Character Counter
TextAreaInputWithCharacterCounter is a headless component that wraps a native <textarea> and a character counter caption inside a <div>. The counter displays "[number] of [maximum] characters" below the text-area-input and updates reactively as the user types.
This component is useful for feedback forms, comment fields, bio inputs, and any interface where users need to know how many characters they have used relative to a maximum limit.
Implementation Notes
- Renders a wrapper
<div>containing a<textarea>and a character counter caption - The character counter displays "[number] of [maximum] characters" below the text-area-input
- The counter updates reactively as the user types
- Uses
aria-describedbyto link the text-area-input to the counter for screen readers - The counter uses
aria-live="polite"so screen readers announce changes - Supports two-way binding on the
valueprop - The
counterTemplateprop allows customization of the counter text for internationalization - Spreads
restPropsonto the wrapper<div>for consumer extensibility
Props
label: string (required) -- accessible name for the text-area-input viaaria-labelvalue: string (default: "") -- bindable text-area-input valuemaxLength: number (required) -- maximum number of characters allowedcounterTemplate: string (default: "{count} of {max} characters") -- template for the counter textrows: number (optional) -- number of visible text rowsplaceholder: string (optional) -- placeholder text for the text-area-inputrequired: boolean (default: false) -- whether the text-area-input is requireddisabled: boolean (default: false) -- whether the text-area-input is disabled...restProps: unknown -- additional attributes spread onto the wrapper<div>
Usage
Basic feedback field with a 500-character limit:
<TextAreaInputWithCharacterCounter label="Feedback" maxLength={500} />
Referral notes with hint text, pre-filled value, and visible rows:
<TextAreaInputWithCharacterCounter
label="Additional notes for the referral"
value={referralNotes}
maxLength={500}
rows={6}
placeholder="Include any relevant medical history or current medications"
/>
User bio with two-way binding:
<TextAreaInputWithCharacterCounter label="Bio" value={bio} maxLength={200} rows={4} placeholder="Tell us about yourself" />
Internationalized counter text (French):
<TextAreaInputWithCharacterCounter label="Commentaire" maxLength={300} counterTemplate="{count} sur {max} caractères" />
Keyboard Interactions
- Tab: Moves focus to and from the text-area-input (native browser behavior)
- Standard text-area-input keyboard interactions (native browser behavior)
ARIA
aria-label={label}-- provides an accessible name for the text-area-inputaria-describedby-- links the text-area-input to the character counteraria-live="polite"-- on the counter so screen readers announce updates
When to Use
- Use when you need a multi-line text input with a visible character limit indicator.
- Use for free-text fields where a maximum length is required, such as referral notes, feedback forms, or medical history descriptions.
- Use when the character count message "[number] of [maximum] characters" provides useful feedback during entry.
- Use when you want the text-area-input and counter linked automatically via
aria-describedbywithout manual wiring.
When Not to Use
- Do not use when no character limit is needed — use TextAreaInput on its own.
- Do not use for single-line inputs — use TextInput instead.
- Do not use when a word count is more appropriate than a character count — adapt CharacterCounter with word counting logic.
- Do not use when the counter needs to be positioned independently from the text-area-input — use CharacterCounter as a standalone component.
Headless
This headless component provides a <div> wrapping a native <textarea> and a character counter <span> with aria-describedby linking, aria-live="polite" for announcements, and a configurable counter template. The consumer provides all visual styling.
Styles
The consumer provides all CSS styling. The component renders with a .text-area-input-with-character-counter class for targeting. No default styles are included -- this is a fully headless component.
Testing
- Verify the component renders a wrapper
<div>with classtext-area-input-with-character-counter - Verify it contains a
<textarea>witharia-labelandaria-describedby - Verify it contains a counter element with
aria-live="polite" - Verify the counter displays the correct character count
- Verify the counter updates as the user types
- Verify
maxLengthis applied to the text-area-input - Verify
disabledandrequiredattributes propagate correctly - Verify the
counterTemplateprop customizes the counter text
Advice
- Designers: Position the counter below the text-area-input. Consider visual feedback when nearing the limit (the consumer can style based on character count).
- Developers: The
counterTemplateprop uses{count}and{max}placeholders. Override for internationalization.
Related components
text-area-input— a multi-line text input areacharacter-counter— a counter showing remaining or used characters in a text fieldtext-input— a single-line text input field <input type="text">
References
- MDN text-area-input: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/text-area-input
- WAI-ARIA aria-describedby: https://www.w3.org/TR/wai-aria-1.2/#aria-describedby