
大家好!😁。
Code Review 的时候,我最怕看到什么?
不是复杂的算法,也不是什么正则。而是明明一个 HTML 标签就能搞定的事,有人非要写几百行 JS + CSS 去重新发明轮子 。
前几天,我看到一个新同学为了写一个折叠面板(Accordion),引入了一个重型的第三方库,还写了一堆 useState、onClick 和动画逻辑。
我默默地把他的代码全删了,换成了 3 行 <details>。他看我的眼神,仿佛在看一个外星人🤣。
在 2025 年的今天,浏览器原生 HTML 的能力早已今非昔比。很多我们习惯用 JS 去模拟的交互,现在不仅有原生支持,而且性能更好、兼容性更强、无障碍(a11y)更完善。
今天,我就来盘点 5 个被严重低估的HTML标签👇。
<details> & <summary>:折叠组件
你是不是还在写这样的 React 代码?
JavaScript
// JS 模拟版
const [isOpen, setIsOpen] = useState(false);
return (
<div className="accordion">
<div className="header" onClick={() => setIsOpen(!isOpen)}>
点击展开 {isOpen ? '⬆️' : '⬇️'}
</div>
{isOpen && <div className="content">...</div>}
</div>
);
为了这个功能,你还得写 CSS 动画,还得处理键盘事件(Tab 键能不能选到?回车能不能展开?等等)。
HTML 原生写法:
HTML
<details>
<summary>点击展开</summary>
<div class="content">
这里是展开后的内容,原生支持 Ctrl+F 页内搜索!
</div>
</details>
- 没有任何JS:自带点击展开/收起交互。
- 无障碍(a11y)满分:屏幕阅读器能完美识别,Tab 键、回车键原生支持。
- 页内搜索 :这是 JS 模拟版最大的痛点。如果内容被 JS 隐藏了(
display: none),浏览器的 Ctrl+F 往往搜不到。但<details>里的内容,即使折叠,浏览器也能搜到并自动展开!

配合 CSS 👇
css
details {
border: 1px solid #ccc;
border-radius: 6px;
padding: 8px;
}
summary {
cursor: pointer;
font-weight: bold;
}
/* 包住内容,让它能动画高度 */
details > .content {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height .45s ease, opacity .3s ease;
}
/* details 处于 open 状态时 */
details[open] > .content {
max-height: 200px; /* 你内容高度大概多少设多少,足够大即可 */
opacity: 1;
}
依然可以做动画。
<dialog>:弹窗组件
写模态框(Modal)是前端最大的坑之一。你需要考虑:
z-index层级会不会被遮挡?- 点击遮罩层关闭?
- Focus Trap(焦点锁定) :打开弹窗后,Tab 键不能跑到底层页面去。
- 按下
Esc键关闭?
为了解决这些,我们通常会引入 Antd Modal 或者 React Portal。但在轻量级场景下,原生 <dialog> 才是神🫅。
HTML 原生:
HTML
<dialog id="myModal">
<form method="dialog">
<p>这是一个原生模态框</p>
<button>关闭(自动)</button>
</form>
</dialog>
<button onclick="myModal.showModal()">打开弹窗⏏</button>

- Top Layer(顶层特性) :浏览器会把它渲染在所有 DOM 的最上层,彻底无视 父元素的
z-index和overflow: hidden。 - ::backdrop 伪元素:直接用 CSS 定制遮罩层样式。
css
/* 背景遮罩 */
dialog::backdrop {
background: rgba(0, 0, 0, 0.45);
backdrop-filter: blur(3px);
transition: opacity .3s ease;
}
- 原生交互 :自带
Esc关闭,自带焦点管理,表单提交自动关闭。
<datalist>:搜索自动补全
当产品经理要求做一个带搜索建议的输入框时,你的第一反应是不是:"快!引入 Select2 或者 Antd AutoComplete!😖
且慢。如果只是简单的建议列表,几 KB 的 JS 库都显得太重了。
HTML 原生版:
HTML
<label>选择你喜欢的框架:</label>
<input list="frameworks" />
<datalist id="frameworks">
<option value="React">
<option value="Vue">
<option value="Svelte">
<option value="Angular">
<option value="Solid">
</datalist>
- 模糊搜索:浏览器原生支持模糊匹配(打 u 会出来 Vue)。
- 响应式:在手机上,它会调用系统原生的下拉选择 UI,体验比网页模拟的更顺滑。
- 解耦:它只是一个建议列表,用户依然可以输入列表里没有的值(这点和 Select 不同)。
<fieldset> & disabled:一键禁用整个表单
场景 :用户点击提交按钮后,为了防止重复提交,我们需要禁用表单里的所有输入框。
JS 笨办法:
JavaScript
// 还要一个个去拿 DOM,或者维护一个 loading 状态传给所有组件
inputs.forEach(input => input.disabled = true);
buttons.forEach(btn => btn.disabled = true);
HTML 原生写法:
HTML
<form>
<fieldset disabled id="login-group">
<legend>登录</legend>
<input type="text" placeholder="用户名">
<input type="password" placeholder="密码">
<button>提交</button>
</fieldset>
</form>
<script>
// 一行代码搞定状态切换
document.getElementById('login-group').disabled = true;
</script>

这是一个极好的分组 思维。通过给 <fieldset> 设置 disabled,浏览器会自动禁用内部所有的 <input>, <select>, <button>。不用写循环,不用维护复杂的 State。
<input type="file" capture>:H5 调用手机相机
场景 :业务需要用户上传一张照片,可以是相册选的,也可以是当场拍的。
很多新手的反应是:是不是要接微信 JSSDK?是不是要写个 Bridge 调原生 App 能力?
答案是不需要!
HTML 原生:
HTML
<input type="file" capture="environment" accept="image/*">
只要加上 capture 属性,在移动端(iOS/Android)点击上传时,系统会直接拉起相机,而不是让你去选文件。拍完照后,你拿到的就是一个标准的 File 对象。
不需要什么 JS SDK,实现原生级体验👍。
我总是强调 最好的代码,是没有代码!
HTML 标准一直在进化,很多曾经需要重型 JS 才能实现的功能,现在已经成了浏览器的出厂设置了。
使用这些原生标签,不仅能减少打包体积,更能让你的应用在可访问性 和性能上天然领先。
下次再想 npm install 一个 UI 库之前,先查查 MDN。说不定,HTML 早就帮你做好了🤔。