数量输入框必须用 input type="number" 并配 min="1" 和 aria-label,增减按钮需用 aria-controls 和 aria-describedby 关联输入框,清空后需处理 NaN 并重置验证状态,总价区域须加 role="status" aria-live="polite"。

数量输入框必须用 input type="number" 并配 min="1" 和 aria-label
屏幕阅读器无法从视觉按钮“+”“−”中理解操作意图,所以数量控制必须依赖语义明确的表单控件。input type="number" 是唯一被主流读屏软件(NVDA、VoiceOver、TalkBack)原生支持的数量输入类型。但只写这个还不够:min="1" 防止用户输入负数或 0,否则会触发无障碍校验失败;aria-label 必须描述完整上下文,比如 aria-label="无线降噪耳机 数量,当前为1,最小值为1" —— 不能只写“数量”,否则读出来就是“编辑,数量”,用户根本不知道这是哪件商品。
button[data-action] 不能替代数量输入,但要补全 aria-controls 和 aria-describedby
很多实现用两个按钮 + 一个纯文本数字来模拟增减,这在无障碍上是反模式:纯文本不可编辑、不可聚焦、无法被键盘操作。如果真要用按钮(例如为了兼容老浏览器),必须让它们与数量输入框建立明确的 ARIA 关系:
– 增减按钮加 aria-controls="qty-1024",指向对应 input 的 id;
– 同时在 input 上加 aria-describedby="hint-1024",指向一段说明性 <div id="hint-1024">按加号增加数量,减号减少数量</div>;
– 按钮本身必须有 aria-label,如 aria-label="增加 无线降噪耳机 数量",而非“加号”。
手动清空 input 值后,valueAsNumber 变 NaN,得主动 fallback
Safari 和部分安卓 WebView 允许用户长按清空 type="number" 输入框,此时 input.value 是空字符串,input.valueAsNumber 为 NaN。若直接拿这个值去计算小计,总价就变成 NaN,且读屏会读出“无效数字”。正确做法是监听 input 事件后立刻校验:
– 用 parseInt(input.value) || 1 强制 fallback 到 1;
– 或更稳妥地用 input.valueAsNumber ?? 1(注意 ?? 对 NaN 无效,仍需 Number.isNaN() 判断);
– 修改后必须调用 input.setCustomValidity('') 清除可能残留的验证错误状态,否则读屏会持续播报“此值无效”。
总价和小计区域要加 role="status" 和 aria-live="polite"
数量变更后实时更新总价,对视障用户来说不是“视觉反馈”,而是“信息抵达”。单纯改 DOM 文本不会被读屏自动播报。必须把总价容器(比如 <span id="total-price">¥598.00</span>)加上 role="status" aria-live="polite"。这样每次 JS 更新它的 textContent,读屏就会安静地读出新值。注意:
– 不要用 aria-live="assertive",它会打断当前播报,造成干扰;
– 不要把它塞进按钮或链接里,否则焦点逻辑混乱;
– 如果小计也动态更新,每个 .item-subtotal 都得单独加 aria-live="off" + 手动 aria-busy="true" 过渡,否则读屏会抢读一堆中间值。
立即学习“前端免费学习笔记(深入)”;
数量修改的无障碍不是加几个属性就完事,关键在「用户能否不看屏幕,仅靠听和键盘,完成从选商品、改数量、确认小计、核对总价的全流程」。最容易漏的是清空输入后的NaN 处理和总价容器缺少 aria-live —— 这两项一缺,整个购物车对视障用户就等于不可用。



















