Skip to content

TextAreaField 字数统计的 aria-live 区每次击键都整句重播:打一句话读屏被打断 52 次(实测) #3408

Description

@yinlianghui

实施 #3406(字数统计 aria-label 键化)时按 PM 裁定只做键化、不改行为,把 #3406 正文提出的「aria-live + 每击键重算是否本身该重设计」拆出来单独立单,并补上实测。

实测(在 #3406 的分支上跑,行为与 origin/main 一致)

zh 会话下渲染 TextAreaField(maxLength: 500),逐字输入一句 52 字符的普通备注(Follow up with the customer about the renewal quote.),记录每次 change 之后 live region 的可访问名:

{
 "keystrokes": 52,
 "ariaLiveOnRegion": "polite",
 "regionIsAriaHidden": null,
 "textareaDescribedBy": null,
 "announcementsFired": 52,
 "distinctAnnouncements": 52,
 "charsSpokenPerKeystroke": 18,
 "totalCharsSpoken": 979,
 "first": "已输入 1 个字符,最多 500 个",
 "last": "已输入 52 个字符,最多 500 个"
}

即:52 次击键 = 52 次互不相同的整句播报,累计 979 个字符,没有任何 debounce、没有阈值门控。用户想输入的那句话本身只有 52 个字符 —— 播报量是输入量的近 19 倍,且每一次都会打断读屏对用户自己输入内容的回读。

另外两点顺带测到的、与之相关的结构问题:

  1. regionIsAriaHidden: null —— 可见的 {n}/{max} 数字没有 aria-hidden,它既是视觉计数器又是 live region 本体;
  2. textareaDescribedBy: null —— textarea 与这个计数器之间没有 aria-describedby 关联,所以聚焦字段时读屏不会提到「有字数上限」,只有开始打字之后才被动地一遍遍听见。

可达性

只在字段元数据声明了 maxLength 时渲染,且只有读屏用户感知 —— 与 #3406 同一块 DOM、同一批用户。不是死代码,当前每一个带 maxLength 的长文本字段都命中。

候选模式(未裁定,列出来供 triage)

  • A. debounce 后再播报:可见数字标 aria-hidden="true",另起一个 visually-hidden 的 aria-live="polite" 状态节点,输入停顿约 1s 后才更新。这是 GOV.UK Design System 的 character-count 组件采用的形状,也是改动面最小、语义最稳的一种。
  • B. 阈值门控:剩余字数进入末段(如剩余 10% 或最后 20 字)之前 live region 保持静默 —— 计数在快到上限时才真正有信息量。可与 A 叠加。
  • C. 取消 live region,改成 aria-describedby:计数器挂到 textarea 的 aria-describedby 上,聚焦时读一次、之后按需查询,完全不打断输入。信息可得性最好,但失去「接近上限」的主动提示,故通常要配 B。
  • D. 维持现状:显式列出以便被否掉,而不是默认继承。

个人倾向 A + B(先静默、临近上限再以 debounce 后的整句提示),但这是可访问性契约形状问题,不自行裁定

#3406 的关系

不是 #3406 的子问题:PM 已明确把 #3406 的完成范围裁定为「键化,行为不变」,本条落在那条边界之外,故独立立单。也不构成阻塞 —— 本条可以在 #3406 落地前后任一时点做,只是两者会碰同一段 JSX(#3406 的 PR 里已把该 aria-live 属性用测试钉住,做本条时需要一并更新那条 pin)。新键 fields.textarea.characterCount#3406 引入;若采用 B,可能还需要一条「接近上限」的新文案键,同样要十包齐。

未打标签,交 PM triage 定级 —— 我判断它是「当前有人踩得到的具体缺陷」而非观察类,但立单时的严重度判断本就不可靠,不自行加权。


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions