应使用语义化实现通知开关,配绑定,用checked+onchange做受控组件,视觉隐藏时保留可访问性,扩展点击区域并同步aria-live提示。

怎么用 <input type="checkbox"> 表达通知开关
通知开关本质是二元状态(开/关),<input type="checkbox"> 是语义最准确、无障碍支持最完善的选择。别用 <button></button> 或 <div> 模拟,那会破坏屏幕阅读器识别、键盘焦点流和原生表单提交行为。
<p>常见错误现象:<code>aria-checked="true" 配合 <div> 手动切换——看似能读,但无法用空格键触发、不参与 <code>form 序列化、按回车无响应。
- 必须配
<label></label>,且用for属性或包裹方式绑定,否则点击文字不触发开关 - 禁用
role="switch":它仅适用于视觉上像物理开关的控件,且需额外处理aria-checked和键盘事件,纯增加复杂度 - 状态同步靠
checked属性,不是 class 名或自定义 data 属性
checked 和 defaultChecked 到底该用哪个
React 场景下容易混淆。服务端渲染或初始化即确定状态时,用 defaultChecked;运行时受 state 控制、需响应式更新时,必须用 checked + onChange。
错误示例:<input type="checkbox" defaultchecked="{isNotifyOn}"> —— 这会导致 props 变化时 UI 不更新,因为 defaultChecked 只在挂载时生效。
- 受控组件:用
checked={isNotifyOn}+onChange={(e) => setIsNotifyOn(e.target.checked)} - 非受控组件(极少需要):用
defaultChecked,后续靠 ref 操作 DOM - SSR 同构应用中,
defaultChecked的值必须与服务端首次渲染一致,否则 React 会警告“prop mismatch”
为什么不能只靠 CSS 画个开关就完事
纯 CSS 实现的“滑块开关”(比如用 ::before/::after 伪元素盖住原生 checkbox)本身没问题,但很多人漏掉关键链路:隐藏原生控件时用了 display: none 或 visibility: hidden,这会让屏幕阅读器完全忽略它。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
正确做法是用视觉隐藏技术,保留可访问性:
- 用
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;隐藏原生<input> - 确保自定义滑块区域有
tabindex="0"和完整键盘支持(空格键切换、Enter 键触发) - 如果用了
role="switch",必须同步设置aria-checked,且每次切换后手动更新
移动端点击区域太小导致误操作
原生 <input type="checkbox"> 默认点击热区极小(约 12×12px),在手机上极易点空。这不是样式问题,而是 HTML 规范里明确允许 label 扩展可点击区域。
- 把整个开关容器包进
<label></label>,而不是只包文字 - 或者给
<input>设置width/height并用transform: scale()放大,但注意别影响布局流 - 避免用
touch-action: manipulation粗暴提响应速度——它可能屏蔽长按等必要手势 - 测试真机:iOS Safari 对
label包裹svg或flex子项的支持不稳定,建议用块级容器
最常被忽略的是:开关状态变更后,没同步更新 aria-live 区域,导致屏幕阅读器用户不知道已生效。哪怕只是加一句 <div aria-live="polite" class="sr-only">通知已开启</div>,体验就差很多。










