你以为选项卡键盘导航只能靠 JS 写 roving tabindex?今天 scroll-marker-group: tabs 用三行 CSS 把这件事彻底原生化了
写过选项卡的工程师,大概都踩过这个坑——每次加个键盘支持,都要写一套 roving tabindex:记当前索引、监听左右方向键、动态切换 tabIndex、维护 ARIA 属性。代码写完二三十行,回头一看,核心交互逻辑全埋在这些样板代码里了。
Chrome 154 稳定版正式支持了 CSS scroll-marker-group 的 tabs 模式,三行 CSS 把这件事彻底原生化了。
核心区别:roving tabindex vs scroll-marker-group: tabs
传统 roving tabindex 方案(JS):
const tabs = document.querySelectorAll('[role="tab"]');
let currentIndex = 0;
function updateTabs() {
tabs.forEach((tab, i) => {
tab.tabIndex = i === currentIndex ? 0 : -1;
tab.setAttribute('aria-selected', i === currentIndex);
});
}
tabs.forEach((tab, i) => {
tab.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') {
currentIndex = (currentIndex + 1) % tabs.length;
updateTabs();
tabs[currentIndex].focus();
}
// ... ArrowLeft 等同理
});
});
scroll-marker-group: tabs 方案(CSS):
.tab-container {
scroll-marker-group: before tabs;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.tab-item {
scroll-snap-align: start;
scroll-target-item: auto;
}
.tab-item::scroll-marker {
content: "";
width: 12px;
height: 12px;
border-radius: 50%;
background: gray;
}
.tab-item::scroll-marker:current {
background: blue;
}
三行核心声明,浏览器自动接管:tablist/tab/tabpanel ARIA 语义 + roving tabindex 键盘导航 + 方向键循环切换 + 屏幕阅读器树剪枝。
tabs 模式的五个内置能力
tabs 模式不是「简化写法」,是完整的 WAI-ARIA Tabs Pattern 原生化:
语义自动推断: ::scroll-marker-group 获得 tablist 角色,::scroll-marker 获得 tab 角色,对应原素获得 tabpanel 角色——不需要写任何 aria-* 属性。
键盘导航自动实现: 只激活状态下的 ::scroll-marker 是 tab stop,用户用左右方向键在 tab 组内循环切换。Tab 键从当前 marker 直接进入对应 tabpanel 内容——原生支持「tab trap」行为,不需要手动管理焦点。
屏幕阅读器树剪枝: 非活跃 tabpanel 内容自动从无障碍树隐藏,模拟 aria-hidden="true" 或 inert 效果,不需要 JS 控制。
焦点保留规则不同: 激活 link marker 时焦点从 marker 移出;激活 tab marker 时焦点保留在 marker 上——tabs 模式自动遵循 tab 交互规范。
scroll-snap 天然配合: tabs 模式原生支持 scroll-snap-type,滚动和 tab 激活天然对齐,适合做 tabbed carousel。
两个模式怎么选
scroll-marker-group 有两种模式:
- links 模式(默认): 适合导航列表,语义对应
<nav>+ 锚点链接,焦点激活后焦点移到目标元素 - tabs 模式: 适合选项卡界面,语义对应 tablist/tab/tabpanel,焦点保留在 tab 上
选 tabs 模式的判断标准:用户是否在操作「切换面板」——是,就用 tabs。
浏览器支持现状
Chrome 154 稳定版已支持,Firefox/Safari 暂无信号。生产环境使用记得加 @supports 渐进增强:
.tab-container {
scroll-marker-group: before tabs;
}
@supports not (scroll-marker-group: tabs) {
/* 降级方案:传统 JS roving tabindex */
}
三步上手
- 列清单: 找出项目中所有用 roving tabindex 实现的选项卡组件
- 迁一行: 把 JS 键盘逻辑删掉,加上
scroll-marker-group: before tabs - 验证两项: 键盘左右方向键是否循环、Tab 键是否直接进入 tabpanel 内容
CSS 的进化方向越来越清晰:交互逻辑正在被浏览器接管,开发者只需要声明「我要什么」,不需要再写「怎么做」。scroll-marker-group: tabs 是这个方向上最新的例子。
评论区
登录后可评论。