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.
Example
TextAreaInputWithCharacterCounter
Show demo markup
<div class="text-area-input-with-character-counter" aria-label="a multi-line text area input with a caption below that is a character counter "[number] of [maximum] characters"">TextAreaInputWithCharacterCounter</div>
Usage example
<script lang="ts">
import TextAreaInputWithCharacterCounter from "./TextAreaInputWithCharacterCounter.svelte";
let feedback = $state("");
</script>
<TextAreaInputWithCharacterCounter
label="Feedback"
maxLength={500}
bind:value={feedback}
/>
Show Svelte source
// In your Svelte component:
import TextAreaInputWithCharacterCounter from "lily-design-system-svelte-headless/components/TextAreaInputWithCharacterCounter/TextAreaInputWithCharacterCounter.svelte";
<TextAreaInputWithCharacterCounter>
<!-- content -->
</TextAreaInputWithCharacterCounter>
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" />
Props
label: string (required) -- accessible name for the text-area-input via aria-label
value: string (default: "") -- bindable text-area-input value
maxLength: number (required) -- maximum number of characters allowed
counterTemplate: string (default: "{count} of {max} characters") -- template for the counter text
rows: number (optional) -- number of visible text rows
placeholder: string (optional) -- placeholder text for the text-area-input
required: boolean (default: false) -- whether the text-area-input is required
disabled: boolean (default: false) -- whether the text-area-input is disabled
...restProps: unknown -- additional attributes spread onto the wrapper <div>
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-input
aria-describedby -- links the text-area-input to the character counter
aria-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-describedby without 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 class text-area-input-with-character-counter
- Verify it contains a
<textarea> with aria-label and aria-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
maxLength is applied to the text-area-input
- Verify
disabled and required attributes propagate correctly
- Verify the
counterTemplate prop 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
counterTemplate prop uses {count} and {max} placeholders. Override for internationalization.
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-describedby to 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
value prop
- The
counterTemplate prop allows customization of the counter text for internationalization
- Spreads
restProps onto the wrapper <div> for consumer extensibility
Related components
text-area-input — a multi-line text input area
character-counter — a counter showing remaining or used characters in a text field
text-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
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: text-area-input-with-character-counter
- PascalCase: TextAreaInputWithCharacterCounter
- Description: a multi-line text area input with a caption below that is a character counter "[number] of [maximum] characters"
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <div>
- CSS class: .text-area-input-with-character-counter
- Interactive: yes
ARIA
aria-label={label} -- provides an accessible name for the text-area-input aria-describedby -- links the text-area-input to the character counter aria-live="polite" -- on the counter so screen readers announce updates
Keyboard
- Tab: Moves focus to and from the text-area-input (native browser behavior)
- Standard text-area-input keyboard interactions (native browser behavior)
Props
label: string (required) -- accessible name for the text-area-input via aria-label value: string (default: "") -- bindable text-area-input value maxLength: number (required) -- maximum number of characters allowed counterTemplate: string (default: "{count} of {max} characters") -- template for the counter text; {count} and {max} are replaced with actual values rows: number (optional) -- number of visible text rows placeholder: string (optional) -- placeholder text for the text-area-input required: boolean (default: false) -- whether the text-area-input is required disabled: boolean (default: false) -- whether the text-area-input is disabled ...restProps: unknown -- additional attributes spread onto the wrapper <div>