Skip to content

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/LinuxAlt + 键accesskey="s" = Alt+S
macOSControl + 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>

注意事项

  1. 不要滥用正数 tabindex:正数 tabindex 会破坏自然的 Tab 顺序,导致维护困难
  2. accesskey 可能冲突:与浏览器/操作系统快捷键可能冲突
  3. 自定义聚焦元素需要 ARIA:div 等非交互元素需要添加 role 属性
  4. focus-visible 优于 focus:使用 focus-visible 只在键盘操作时显示焦点环

最佳实践

  1. 使用 tabindex="0":使自定义组件可聚焦
  2. 使用 tabindex="-1":实现焦点陷阱和程序化聚焦
  3. 避免正数 tabindex:保持自然的 DOM 顺序
  4. 提供视觉焦点指示:所有可聚焦元素都应有可见的焦点样式
  5. 模态框管理焦点:打开时聚焦到模态框,关闭时恢复

下一节

继续学习:draggable 拖拽

参考链接