热词列表必须用<ul class="hot-tags">包裹,每个热词为<li>内含带href的<a>;需转义用户内容防XSS,支持键盘操作与屏幕阅读器,通过history.pushState管理路由并主动聚焦结果。

搜索热词不是靠 <mark> 做容器,也不是用 <strong> 或 <span> 平铺堆出来——它本质是一个语义化列表,需兼顾可访问性、XSS 防御和键盘操作支持。
热词列表该用什么 HTML 结构
必须用 <ul class="hot-tags"> 包裹,每个热词是独立的 <li>,里面放带 href 的 <a>。别用 <div> 或 <p> 模拟列表,屏幕阅读器无法识别项数与顺序。
-
href设为"/search?q=前端"是最干净的做法;若只触发 JS 不跳转,用href="#"+event.preventDefault(),否则页面会滚到顶部 - 每个
<a>加role="button"和tabindex="0",确保键盘用户能聚焦(尤其当 CSS 把它渲染成无下划线块状时) - 若支持删除(如用户自定义热词),每个
<li>内补一个<button type="button" aria-label="删除 前端">×</button>
动态插入热词时怎么防 XSS
后端返回的热词名可能含 <、& 或脚本片段,直接 innerHTML = "<a>" + tag + "</a>" 就等于开后门。
- 先对原始字符串做 HTML 实体转义:
&→&,<→,<code>>→> - 再用
document.createTextNode()创建文本节点,或用textContent设置链接文字 - 结构部分(如
<a href="/search?q=xxx">)用insertAdjacentHTML('beforeend', htmlString)插入,但确保htmlString中不含用户可控内容 - 禁用
document.write()、eval()、new Function()—— 旧 CMS 插件里仍有这类写法,2026 年仍有人踩
点击热词后如何正确触发搜索
不能只改 location.hash 或拼接 location.href,得让浏览器历史、键盘导航、刷新分享全部正常工作。
立即学习“前端免费学习笔记(深入)”;
- 监听
click和keydown(Enter/Space),统一调用搜索函数 - 搜索动作发生后,用
history.pushState({ q: "前端" }, "", "/search?q=前端")更新地址栏 - 监听
popstate,从 URL 参数还原当前热词状态,并高亮对应项 - 焦点必须主动管理:搜索完成后,调用
document.getElementById("search-results").focus(),否则键盘用户卡在空白页
为什么不用 <mark> 做热词容器
<mark> 是语义化高亮元素,只用于标记“当前上下文中已匹配的关键词”,比如搜索结果里的高亮文字。它不表达“列表”“可操作项”“数据源”任何一层含义。
- 用
<mark>包热词,AT(辅助技术)会读作“高亮 前端”,而非“热门搜索词 前端” - 没有默认焦点能力,无法被键盘 tab 到,
role和tabindex加了也白加 - CSS 重置后容易丢失语义,且多数无障碍检测工具会报错:“
<mark>用在非上下文高亮场景”
真正难的不是渲染出几个带颜色的文字,而是让每个热词在键盘、屏幕阅读器、URL 分享、历史回退、XSS 防御这五条线上同时站稳——漏掉任何一条,都算没做完。



















