最稳妥方案是用隐藏但可访问的radio按钮配合label,通过:checked和~选择器控制样式,确保name统一、value连续、id与for精确匹配,并用clip-path或mask实现半星,兼顾键盘导航与屏幕阅读器支持。

用 input[type="radio"] 实现可点击的星级评分
纯 CSS 星级组件最稳妥的方案是基于原生单选按钮,而不是用伪元素模拟点击区域——后者在触屏设备上容易失焦或触发两次。关键点在于把 input 隐藏但保留可访问性,用相邻兄弟选择器控制后续 label 的样式。
- 每个星必须对应一个带
name属性的input[type="radio"],且value为 1–5(不能跳号) - 用
input + label::before控制星星显示,避免用label > input嵌套结构,否则 iOS Safari 会忽略点击 -
input必须放在label之前,否则+选择器失效 - 给
label加cursor: pointer,不然部分安卓浏览器不显示手型
示例结构:
<input type="radio" name="rating" value="1" id="star1"> <label for="star1"></label> <input type="radio" name="rating" value="2" id="star2"> <label for="star2"></label>
用 clip-path 或 mask 实现半星评分
整星容易,半星才是真考验。别用两个重叠的 span 拼接——响应式缩放时边缘对不齐。推荐用 clip-path 切割单个星图标,或者用 mask 配合渐变遮罩。
-
clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%)可切左半星,但需确保星图是正方形且居中 - 更稳的方式是用
mask:定义一个线性渐变遮罩,mask-image: linear-gradient(90deg, black 50%, transparent 50%) - 注意
mask在 Firefox 中需要-webkit-mask前缀,且不支持mask-border - 不要用
transform: scaleX(0.5),它会让文字缩放、模糊,且无法精准控制切割位置
处理键盘操作与屏幕阅读器兼容性
光能点没用。用户按 Tab 进入评分区后,得能用方向键切换、空格确认,还要让读屏软件知道当前值。这一步常被跳过,导致 WCAG 2.1 AA 不达标。
立即学习“前端免费学习笔记(深入)”;
- 所有
input必须有aria-label,比如aria-label="1星",不能只靠视觉隐藏 - 用
:focus-within给整个评分组加外边框,否则键盘用户找不到焦点在哪 - 禁用
user-select: none,否则 VoiceOver 在 iOS 上无法选中 - 如果默认值为 3 星,要提前给对应
input加checked属性,而不是靠 JS 设置
避免 pointer-events: none 导致的交互失效
有人为了“让背景星不响应点击”给未选中的星加 pointer-events: none,结果发现鼠标悬停动画没了,甚至整个组件点不动——因为事件被父容器拦截了。
- 正确做法是只对未激活的
label设pointer-events: auto,而激活态用z-index提升层级 - 更简单的是不用
pointer-events,改用opacity和filter: grayscale()视觉降级 - 若必须禁用,只对
input:not(:checked) + label加pointer-events: none,且确保父容器没有pointer-events: none - 测试时用 Chrome DevTools 的“Rendering”面板勾选 “Paint flashing”,看悬停时是否触发重绘
半星精度、键盘导航、触屏点击这三个点,任何一个没对齐,用户就会卡在评分环节。尤其别信“CSS-only 就够了”的说法——至少得预留 change 事件钩子给 JS 补位。


















