Search topics
Character counters that stay silent or speak on every key
A gift message counter changes on screen only, or in an assertive live region on every key, so a screen reader says nothing, or a number after each letter.
count.textContent = countText(100 - gift.value.length); The problem
Screen reader users can't tell how much room is left in a field with a limit, or hear a number after every letter they type.
A small homeware shop lets people add a gift message at checkout. The box takes up to 100 characters, and under it, on the right, a line counts down as people type: "100 characters left", then 99, then 98. A sighted customer writes "Happy birthday! May this tea set make your Sunday mornings slow and warm. Love, Jo xx", glances down, sees "15 characters left", and knows there is room for one more short line.
A screen reader user writing the same message hears their own letters, and nothing else. They don't know the box has a limit, or how close they are to it. When they reach 100 characters, the field stops taking letters and nothing says why: the end of the sentence they were typing is simply not there. To find out how much room is left, they have to stop, leave the field and read the page around it.
On a second version of the shop, the counter is an assertive live region. Now every key brings a number: "14 characters left", "13 characters left", "12 characters left". Depending on the screen reader, each count may be read after the letter, or started and cut short by the next key. Either way, a person typing a sentence hears a count after nearly every letter, and loses track of the words they are writing.
WCAG defines a status message as a change in content that is not a change of context and that tells people about the results of an action, a waiting state, the progress of a process or errors. "15 characters left" reports the result of each key typed, and it changes without taking focus. Success Criterion 4.1.3 asks that such messages can be determined through role or properties, so that assistive technology can present them without moving focus.
In the first Before, the count is a plain p. Its text changes on screen, but nothing tells assistive technology to read the change, which is the failure W3C describes in F103. The second Before gives the counter aria-live="assertive". That does tell the screen reader about each change, as a message to present at once, and the script writes a new count on every key. Understanding 4.1.3 lists role="alert" or aria-live="assertive" on content that is not important and time-sensitive among its failures, and says status messages should reach people without unnecessarily interrupting their work. A count of characters left is routine: it can wait until the person stops typing.
Limits are common in gift messages, reviews, profile bios and support forms, and the counter is often the only place on the page that states the limit at all.
2 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Without vision Screen reader users hear their own letters and nothing about the count, so they learn the message was too long only when the field stops taking letters, with nothing said. With the count in an assertive region, they hear a number after nearly every letter, and the words they are writing are lost under it.
- Limited vision Screen magnifier users zoomed in on the words they are typing may not see the counter at the edge of the box, and those who use speech with their magnifier hear nothing about it either.
A scan loads the page and finds "100 characters left" in the markup. It never types in the field, so it never sees the line change, and nothing in the HTML says the line is a count of what is typed. EaseWeb's audit does type: it fills a field that has a counter to 10 characters short of its limit, types three letters, and watches the live regions. A count rewritten in an assertive region on each of the three keys is a finding. A count rewritten in a polite region on each key, or a counter that changed with nothing written to any live region, while the keys went in or in the pause after, is a lead for a person to check with a screen reader. AI assistants asked to make a counter accessible often add aria-live to the visible counter, polite or assertive. That makes it heard, on every key, which is the second Before. Only typing into the field with a screen reader running shows what is said, and when.
Try it
Finish the gift message, then stop typing and listen.
Broken 1: type a few words, and keep going until the box is full. The count under the box drops, and the panel shows what a screen reader says.
Broken 2: type a few words. How many times is the count read?
Apply the fix. Type a word, stop for a second, then type on to the limit and stop again.
Broken Can you fix this?
15 characters left
Screen reader says:
(Click in the box and type)
The fix
Say the count once typing pauses near the limit.
A counter that changes on screen only
<label for="gift">Gift message</label><textarea id="gift" name="gift" maxlength="100"></textarea><p id="gift-count" class="count">100 characters left</p>What is wrong1 point
The count is right, and it sits right under the box. One thing is wrong.
- The
pis not a live region, so the count changes on screen and is never announced, and when the box is full, nothing says why it stopped taking letters.
<label for="gift">Gift message</label><p id="gift-hint" class="hint">You can enter up to 100 characters.</p><textarea id="gift" name="gift" maxlength="100" aria-describedby="gift-hint"></textarea><p id="gift-count" class="count" aria-hidden="true">100 characters left</p><p id="gift-status" class="visually-hidden" role="status"></p>How it is fixed4 steps
- Put the limit in a hint above the box and tie it to the field with
aria-describedby. The screen reader reads "You can enter up to 100 characters" when the person reaches the field, before they type a word. - Take
aria-liveoff the visible counter and give itaria-hidden="true". It still changes on every key for people who see it, but it is no longer read on each key, or a second time when someone reads the page. - Add one empty
pwithrole="status", visually hidden, that is on the page from the start, so writing into it is a change the screen reader announces, once it has finished what it is saying. - Write the count into it once typing has paused for a second, and only from 20 characters left. A person typing hears nothing about the count until they stop near the limit, then hears "12 characters left" once. Further from the limit, the region is emptied, so the next count near it is a change again.
This fixes both Befores and passes 4.1.3. The count is a status message given to the screen reader through its role, politely, once per pause, instead of never or after every key.
Why this fix: Say it once, when the change has settled
How long to wait. The design systems that do this wait about a second. GOV.UK's character count updates its screen reader message once no key has been typed for half a second, checking every second, and the U.S. Web Design System's waits 1.2 seconds after the last key. Much shorter, and a slow typist hears a count between words; much longer, and the count arrives after they have moved on.
From when to speak. A count read from the first letter is noise for anyone writing a short message. GOV.UK's component has a threshold option that shows and announces its count only once a set share of the limit is used, such as 75%; this example speaks from 20 characters left. Because the hint states the limit when the field is reached, nobody needs the count before it is near.
A polite counter on every key. The more common form of the second Before is the same counter with aria-live="polite", which some component libraries render while the field has focus. Each change is announced, so it meets the words of 4.1.3, but it still sends a message on every key, and in some screen readers a count is read after nearly every letter. The fix is the same: one hidden region, written after a pause.
Over the limit instead of cut off. With maxlength, the browser stops taking letters at the limit and cuts off anything pasted beyond it, without a word. GOV.UK's component leaves maxlength off and lets people go over, so they can type or paste their whole answer and then edit it down, with the count saying how many characters too many. If you do that, the form still has to stop and explain on submit, and errors shown while typing are covered in inline form errors read out while typing, or not at all.
Count what the limit counts. maxlength and value.length count UTF-16 code units, so an emoji can count as two characters. A server that limits bytes counts most Japanese characters as three, in UTF-8. Count in the same unit as whatever enforces the limit, and say that unit in the hint.
Speech input. Some speech recognition software changes a field's value without the events a script listens for. GOV.UK's component also checks the value once a second while the field has focus, so a dictated sentence still updates the count.
Not fixes. Pointing the field's aria-describedby at the changing counter gets it read when the person reaches the field, but not as they type, so the count they hear is the one from before they started. role="alert" has the same problem as the second Before. Moving focus to the counter takes the caret out of the box mid-sentence.
Design ideas
A counter can take any shape, as long as it has words to say.
Four more ways to draw the gift message counter, each live: type in them. Three say the count in words once you stop near the limit; one draws it only as a bar, and nothing is ever said.
In the design file
- Write the words a screen reader says for the count, such as “15 characters left”, beside a counter drawn as a fraction, a ring or a bar.
- Note that those words are said once typing stops, and only near the limit, such as from 20 characters left.
- Put the limit in the hint under the label, so it is read when people reach the box.
- If people may go over the limit, draw the box over it too, with the words for how many characters too many.
Passes
You can enter up to 100 characters.
Count near the limit, and how far over Nothing under the box until 20 characters are left, then “15 characters left”; past the limit, “3 characters too many” in red, with the box outlined, so people can paste their whole message and cut it down. The words are said once typing stops.Rule of thumb: Say it once, when the change has settled
You can enter up to 100 characters.
Fraction in the corner “85 / 100” in small grey type under the box, the count and the limit as Material and Ant Design draw them. Read on every key it would be noise, so it stays silent: the limit is in the hint, and “15 characters left” is said once typing stops near the limit.Rule of thumb: Say it once, when the change has settled
You can enter up to 100 characters.
Ring that fills, with the number near the limit A small ring fills as the message grows and shows the number inside once the limit is near, as X’s post box does. It moves only while letters go in and stops with them. The ring itself says nothing; “15 characters left” is said in words once typing stops.Rule of thumb: Move to show a change, then stop
Fails
- Bar with no numberWCAG 4.1.3 A thin bar fills under the box and turns amber near the limit. It looks calm, but there is no number and no words anywhere, so nothing can be read: a screen reader user learns the box is full only when it stops taking letters.
Verify the fix
4 checks, no mouse.
Test with: Screen reader, Keyboard
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the field component, its counter or the limit changes.
How screen readers handle aria-live="assertive", role="status" and aria-describedby
Limits
Live regions behave differently across screen readers and browsers, and some drop a polite message when people start typing again before it is read. This pattern covers one field with a fixed limit in characters; a limit in words works the same way, with words counted instead. Other status messages, such as a saved draft, are covered in status messages that are shown but never announced. Test your real field, with its real limit, hint and error messages, with the screen readers your users use.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
EaseWeb service
Fixed it? Keep it from coming back.
I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.