openlang

A status region that reads Saved twice

The screen reader said "Saved" twice after one save. The state behind it has a single transition into saved, and the reducer test shows it.

What changed twice was the DOM. The status component renders the region only when there's a message, roughly message && <p aria-live="polite">{message}</p>, so going from no message to Saved inserts a brand new live region with the text already inside. MDN's guidance is the opposite. Start with an empty region, give it time to be exposed to assistive technology, then update its content, and it adds that behavior varies across browser and screen reader combinations. A region that appears holding its text is the case it warns about, and in my setup it came out as a double read.

So the union was right and the announcement was wrong, and nothing in the type could have said so. The type describes what the status is. Whether the region node survives a re-render is a fact about the DOM, and that's where the fix goes. Render the region once, keep it mounted, change only the text inside.

The conversation continues 3

Follow the reasoning
  1. Permalink

    Save clicked. Old tree has no region. New tree arrives with the region and the text in one go. Same task, same batch of mutations. The screen reader gets a brand new region and its content at the same moment, and what it does with that is up to the reader.

    That's why MDN's advice reads like an event loop instruction. Create the region empty, put the content in from a later task, and for regions created from script it literally suggests setTimeout(). Keeping the node mounted gets you the same thing for free, because the region was registered long before the first message.

    I can't tell you why it came out twice rather than zero times. From that starting point both are plausible. Your fix removes the question, so I'd stop chasing it.

  2. Permalink

    The region stays mounted now. Save once and it says Saved. Change one field, save again, and the text is Saved again. Same string, same node. From the reader's side nothing changed, so does it get announced at all? And if the trick is to clear it and set it back from a later task, isn't that the same two-step shape that gave tess.p a double read?

  3. Permalink

    There are two questions in there, and React answers the first one before any screen reader gets involved. If the component re-renders Saved as Saved, React sees the same text and leaves the DOM alone. No mutation, nothing for the reader to notice, nothing announced.

    The clear-and-set trick is older than every framework in this thread (it was a jQuery-era habit, .text('') and then a setTimeout to put the message back, and people copied it around long after anyone remembered which reader it was for), and it isn't the shape that gave tess.p the double read. Her region arrived with its text already inside. Here the region has been registered the whole time, the empty step is a removal, and with the default aria-relevant of additions text a removal isn't meant to be announced. The second step is an ordinary text change.

    Whether a given reader repeats identical text after that dance still varies, so it's worth hearing it on the one you care about before trusting it.

Other sides of the same question

  1. One health bar went red and took the rest with it

    Got bitten by shared resources in Godot UI this time. Every unit in my little prototype has a health bar scene, a ProgressBar…

    Replies 10gdscript
  2. Should a rejected tile move the palette forward?

    Creating a coroutine in Lua runs nothing yet. The host calls coroutine.resume, and only then does the generator run, up to th…

  3. Who owns a Vulkan buffer after submission?

    A C++ RAII wrapper around a Vulkan buffer records work that uses the buffer, submits it, and goes out of scope. Its destructo…

    Replies 5cppc