tabindex 与 accesskey
tabindex 全局属性控制元素的 Tab 键聚焦顺序和可聚焦性。accesskey 全局属性为元素指定键盘快捷键,允许用户通过键盘快速操作元素。
前置知识
阅读本节前,建议先了解:dir 文本方向
基础概念
tabindex
tabindex 决定元素是否可以聚焦以及 Tab 键导航的顺序。
| 值 | 行为 | 说明 |
|---|---|---|
| 负数(如 -1) | 可聚焦但不参与 Tab 顺序 | 需要 JS 调用 focus() |
| 0 | 参与自然 Tab 顺序 | 按 DOM 顺序排列 |
| 正数 | 自定义 Tab 顺序 | 不推荐,破坏自然顺序 |
html
<!-- 正常 Tab 顺序 -->
<button>按钮 1</button>
<button>按钮 2</button>
<!-- 不参与 Tab 顺序 -->
<div tabindex="-1" id="scroll-container">可聚焦但 Tab 跳过</div>
<!-- 使非交互元素可聚焦 -->
<div tabindex="0" role="button" onclick="doAction()">点击我</div>accesskey
accesskey 为元素分配一个键盘快捷键。不同操作系统触发方式不同:
| 操作系统 | 触发方式 | 示例 |
|---|---|---|
| Windows/Linux | Alt + 键 | accesskey="s" = Alt+S |
| macOS | Control + Option + 键 | accesskey="s" = Ctrl+Option+S |
html
<button accesskey="s">保存 (Alt+S)</button>
<a href="/" accesskey="h">首页 (Alt+H)</a>
<input accesskey="n" placeholder="搜索 (Alt+N)">语法与使用
tabindex 详解
html
<!-- tabindex="-1":不参与 Tab 顺序 -->
<div tabindex="-1" id="modal-content">
<p>模态对话框内容。需要 JS 调用 focus() 才能聚焦。</p>
</div>
<!-- tabindex="0":参与 Tab 顺序 -->
<div tabindex="0" role="slider" aria-label="音量">
音量滑块
</div>
<!-- 不推荐:自定义正数顺序 -->
<input tabindex="3">
<input tabindex="1">
<input tabindex="2">
<!-- 浏览器会按 1->2->3 的顺序导航 -->JavaScript focus() 方法
javascript
// 程序化聚焦
document.getElementById("my-element").focus();
// 检查当前聚焦元素
console.log(document.activeElement);
// 焦点管理:打开模态框时
function openModal(modalId) {
const modal = document.getElementById(modalId);
modal.hidden = false;
const firstFocusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (firstFocusable) firstFocusable.focus();
}
// 关闭模态框时恢复焦点
let previousFocus = null;
function closeModal(modalId) {
previousFocus = document.activeElement;
document.getElementById(modalId).hidden = true;
if (previousFocus) previousFocus.focus();
}详细说明
tabindex="0" vs tabindex="-1"
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tabindex 示例</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; padding: 2rem; }
.custom-btn {
display: inline-block; padding: 0.75rem 1.5rem;
background: #3b82f6; color: #fff; border-radius: 6px;
cursor: pointer; outline: none;
}
.custom-btn:focus {
box-shadow: 0 0 0 3px rgba(59,130,246,0.4);
}
.focus-demo {
margin: 1rem 0; padding: 1rem;
border: 1px solid #e2e8f0; border-radius: 6px;
}
.focus-ring {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
button:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
</style>
</head>
<body>
<h1>tabindex 示例</h1>
<div class="focus-demo">
<h2>自然 Tab 顺序</h2>
<button>按钮 1</button>
<input type="text" placeholder="输入框">
<button>按钮 2</button>
</div>
<div class="focus-demo">
<h2>tabindex="0":自定义可聚焦元素</h2>
<div class="custom-btn" tabindex="0" role="button" onclick="alert('clicked')">
这是一个 div 按钮(可 Tab 聚焦)
</div>
</div>
<div class="focus-demo">
<h2>tabindex="-1":JS 焦点管理</h2>
<button onclick="focusTarget()">聚焦到下方文本</button>
<div id="target" tabindex="-1" class="focus-ring" style="padding:1rem;margin-top:0.5rem;background:#f0f9ff;">
这个区域只能通过 JS 聚焦,不能 Tab 到达。
</div>
</div>
<script>
function focusTarget() {
document.getElementById('target').focus();
}
</script>
</body>
</html>accesskey 的注意事项
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>accesskey 示例</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; padding: 2rem; }
.nav-bar { display: flex; gap: 1rem; padding: 1rem; background: #f1f5f9; border-radius: 8px; margin-bottom: 1rem; }
.nav-bar a { padding: 0.5rem 1rem; text-decoration: none; color: #1e293b; }
.nav-bar a:focus { outline: 2px solid #3b82f6; }
.shortcut-hint { font-size: 0.75rem; color: #94a3b8; }
</style>
</head>
<body>
<h1>accesskey 示例</h1>
<nav class="nav-bar" aria-label="快捷键导航">
<a href="#" accesskey="h">首页 <span class="shortcut-hint">(Alt+H)</span></a>
<a href="#" accesskey="s">搜索 <span class="shortcut-hint">(Alt+S)</span></a>
<a href="#" accesskey="n">新建 <span class="shortcut-hint">(Alt+N)</span></a>
<a href="#" accesskey="p">打印 <span class="shortcut-hint">(Alt+P)</span></a>
</nav>
<p>提示:按住 Alt 键加对应字母可以快速跳转。</p>
<p>注意:accesskey 可能与浏览器快捷键冲突(如 Alt+S 可能触发书签保存)。</p>
</body>
</html>实战示例
模态对话框的焦点管理
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>焦点管理示例</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; padding: 2rem; }
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
display: none; align-items: center; justify-content: center;
}
.overlay.active { display: flex; }
.modal {
background: #fff; padding: 2rem; border-radius: 12px;
max-width: 400px; width: 90%;
}
.modal h2 { margin-bottom: 1rem; }
.modal input { width: 100%; padding: 0.6rem; margin-bottom: 1rem; border: 1px solid #d1d5db; border-radius: 6px; }
button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
.btn { padding: 0.6rem 1.5rem; border: none; border-radius: 6px; cursor: pointer; }
.btn-primary { background: #3b82f6; color: #fff; }
.btn-cancel { background: #f1f5f9; color: #475569; margin-right: 0.5rem; }
.btn-group { display: flex; justify-content: flex-end; gap: 0.5rem; }
</style>
</head>
<body>
<h1>焦点管理示例</h1>
<button class="btn btn-primary" onclick="openModal()">打开对话框</button>
<div class="overlay" id="overlay">
<div class="modal" role="dialog" aria-modal="true" aria-label="登录对话框">
<h2>用户登录</h2>
<label for="username">用户名</label>
<input type="text" id="username" autofocus>
<label for="password">密码</label>
<input type="password" id="password">
<div class="btn-group">
<button class="btn btn-cancel" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="closeModal()">登录</button>
</div>
</div>
</div>
<script>
let lastFocused = null;
function openModal() {
lastFocused = document.activeElement;
const overlay = document.getElementById('overlay');
overlay.classList.add('active');
// 聚焦到第一个输入框
document.getElementById('username').focus();
// 阻止 Tab 跳出对话框
overlay.addEventListener('keydown', trapFocus);
}
function closeModal() {
const overlay = document.getElementById('overlay');
overlay.classList.remove('active');
overlay.removeEventListener('keydown', trapFocus);
// 恢复之前的焦点
if (lastFocused) lastFocused.focus();
}
function trapFocus(e) {
if (e.key === 'Tab') {
const modal = document.querySelector('.modal');
const focusable = modal.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])');
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
}
}
</script>
</body>
</html>注意事项
- 不要滥用正数 tabindex:正数 tabindex 会破坏自然的 Tab 顺序,导致维护困难
- accesskey 可能冲突:与浏览器/操作系统快捷键可能冲突
- 自定义聚焦元素需要 ARIA:div 等非交互元素需要添加 role 属性
- focus-visible 优于 focus:使用
focus-visible只在键盘操作时显示焦点环
最佳实践
- 使用 tabindex="0":使自定义组件可聚焦
- 使用 tabindex="-1":实现焦点陷阱和程序化聚焦
- 避免正数 tabindex:保持自然的 DOM 顺序
- 提供视觉焦点指示:所有可聚焦元素都应有可见的焦点样式
- 模态框管理焦点:打开时聚焦到模态框,关闭时恢复
下一节
继续学习:draggable 拖拽