必须用而非或,因商品是可独立分发、可重用、有作者和时间的语义单元;所有卡片须包裹于中且每个为直接子元素;价格、按钮、状态须绑定语义标签并确保可访问性。

产品展示卡片必须用 <article></article>,不能用 <div> 或 <code><section></section> —— 这不是风格偏好,而是语义刚性要求。
为什么必须用 <article></article> 而不是 <section></section>
<article></article> 表示「可独立分发、可重用、有明确作者和发布时间」的内容单元;电商商品完全满足:每件商品可单独 RSS 抓取、可分享链接、可被搜索引擎作为独立条目索引。<section></section> 只是逻辑分组,没有独立性含义——把它套在单个商品上,等于告诉机器“这只是一个章节”,而非“这是一个商品”。
常见错误现象:
– 商品卡片外层用了 <section class="card"></section>,导致屏幕阅读器读作“区域”,无法跳转到具体商品;
– 多个商品堆在同一个 <section></section> 里,破坏了每个商品的语义边界。
实操建议:
• 每个商品卡片必须包裹在独立的 <article></article> 中
• <article></article> 必须带唯一 id(如 id="product-789")
• 内部至少含一个 <h3></h3>(商品名),不能只有图片和价格
<ul></ul> 包裹所有商品卡片是硬性可访问性要求
商品列表不是「视觉网格」,而是「无序集合」。用 <div class="grid"> 堆砌卡片,屏幕阅读器只会逐字读出“division”“division”……用户无法感知这是 24 件商品的列表,也无法用快捷键(如 J 键)跳转下一项。
<p>使用场景明确:<br>– 所有商品卡片必须由 <code><ul class="product-list"></ul> 包裹
– 每个 <article></article> 必须是 <li> 的直接子元素(即 <li><article>…</article></li>)
– 禁止用 <ol></ol>:商品排序是动态筛选结果,不构成固有顺序
性能影响小,但兼容性关键:老版本 NVDA、JAWS 都依赖 <ul></ul> + <li> 组合识别列表结构;缺失时键盘用户需手动 tab 遍历整个页面。
卡片内部关键元素的语义绑定不可省略
价格、按钮、状态这些纯视觉信息,若不绑定语义标签,对屏幕阅读器就是“哑巴内容”。
容易踩的坑:
– 价格写成 <p>¥299</p>:无上下文,读屏无法判断这是哪个商品的价格
– “加入购物车”用 <div onclick="add()">:不是可聚焦按钮,键盘用户无法操作<br>– “缺货”仅靠红色文字提示:颜色变化对色盲用户无效,且读屏不会播报
<p>正确做法:<br>• 价格用 <code><span itemprop="price">¥299</span> 或嵌入 <strong></strong> + 视觉隐藏文本
• 操作按钮必须是 <button type="button">加入购物车</button>
• 状态用 <span aria-live="polite">缺货</span> 并配合 DOM 动态更新
最常被忽略的一点:每个 <article></article> 应该有隐含或显式的发布/更新时间,哪怕只是占位 <time datetime="2026-08-25"></time>。这不是为了 SEO 排名,而是让辅助技术能建立内容时效模型——比如用户问“最新上架的商品”,读屏可据此过滤。











