<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>鍵盤 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E9%8D%B5%E7%9B%A4/</link><description>Recent content in 鍵盤 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 26 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E9%8D%B5%E7%9B%A4/index.xml" rel="self" type="application/rss+xml"/><item><title>Tab Order = DOM Order = Mental Model 三者對齊</title><link>https://tarrragon.github.io/blog/report/tab-order-mental-model-alignment/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/tab-order-mental-model-alignment/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;blockquote>
&lt;p>Tab 順序 = DOM 順序 = 使用者 mental model 的互動順序、三者該對齊。&lt;/p>&lt;/blockquote>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>軸&lt;/th>
 &lt;th>由什麼決定&lt;/th>
 &lt;th>該對齊到什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>DOM 順序&lt;/td>
 &lt;td>HTML / template 結構&lt;/td>
 &lt;td>Mental model 的互動順序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tab 順序&lt;/td>
 &lt;td>DOM 順序（除非 tabindex 強制覆寫）&lt;/td>
 &lt;td>DOM 順序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mental model 順序&lt;/td>
 &lt;td>使用者預期「先做 X 再做 Y」的流程&lt;/td>
 &lt;td>UI 設計意圖&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三者偏差的後果：&lt;/p>
&lt;ul>
&lt;li>DOM ≠ mental model：視覺 / tab 順序跟使用者期望不一致、a11y 體驗差&lt;/li>
&lt;li>DOM ≠ tab order（用 &lt;code>tabindex &amp;gt; 0&lt;/code>）：DOM 改變時 tab 順序維護成本爆炸（#52 反模式）&lt;/li>
&lt;li>全對齊：DOM 簡單、tab 自然、a11y 預設正確&lt;/li>
&lt;/ul>
&lt;p>要解決不對齊、&lt;strong>優先重排 DOM&lt;/strong>、不要用 &lt;code>tabindex&lt;/code> 強制覆寫。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼三者該對齊到-dom-順序">為什麼三者該對齊到 DOM 順序&lt;/h2>
&lt;h3 id="tab-順序跟-dom-順序綁定是-spec-規定">Tab 順序跟 DOM 順序綁定是 spec 規定&lt;/h3>
&lt;p>HTML5 spec：tabbable elements 預設依 source order（DOM 順序）navigate。要改變只能用 &lt;code>tabindex&lt;/code> 覆寫。&lt;/p>
&lt;p>&lt;code>tabindex&lt;/code> 三種值：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>tabindex&lt;/th>
 &lt;th>行為&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>0&lt;/code> 或不寫&lt;/td>
 &lt;td>跟 DOM 順序、可 tab 到（依元素本身的 tabbability）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>-1&lt;/code>&lt;/td>
 &lt;td>不能 tab 到、但可被 &lt;code>.focus()&lt;/code> 程式 focus&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>&amp;gt; 0&lt;/code>（如 &lt;code>1&lt;/code>、&lt;code>2&lt;/code>）&lt;/td>
 &lt;td>強制覆寫順序、所有 &lt;code>&amp;gt; 0&lt;/code> 的元素先 tab、按數值升序&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>tabindex &amp;gt; 0&lt;/code> 反模式（同 &lt;a href="../keyboard-accessibility/">#52 鍵盤可達性&lt;/a>）：&lt;/p>
&lt;ul>
&lt;li>全頁面只要有任何元素用 &lt;code>tabindex &amp;gt; 0&lt;/code>、整個 tab 順序變混亂（其他 &lt;code>0&lt;/code> / 不寫的元素都被推到後面）&lt;/li>
&lt;li>維護成本：DOM 改了、所有 &lt;code>tabindex &amp;gt; 0&lt;/code> 的數值都要重排&lt;/li>
&lt;li>A11y：screen reader 跟視覺使用者體驗到不同順序&lt;/li>
&lt;/ul>
&lt;p>唯一合法用法：要把元素「移出 tab cycle」用 &lt;code>tabindex=&amp;quot;-1&amp;quot;&lt;/code>（例如 modal 開啟時鎖住背景）。&lt;/p>
&lt;h3 id="mental-model-順序由-ui-設計決定">Mental model 順序由 UI 設計決定&lt;/h3>
&lt;p>互動式 UI 隱含一個流程：使用者預期「先做 X 再做 Y」。例如：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>UI 類型&lt;/th>
 &lt;th>預期 mental model 順序&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>搜尋頁&lt;/td>
 &lt;td>1. 打 query → 2. 篩選範圍 → 3. 看結果 → 4. 載入更多&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>表單&lt;/td>
 &lt;td>從上到下、必填欄位先、subtmit 在最後&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Wizard&lt;/td>
 &lt;td>Step 1 → Step 2 → Step 3 → Submit&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品列表&lt;/td>
 &lt;td>1. Sort / filter → 2. 看商品 → 3. 加入購物車&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Modal&lt;/td>
 &lt;td>Modal 內容 → primary action → secondary action → close&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>設計者腦中有這個順序、寫 HTML 時要把它具體化成 DOM 順序。&lt;strong>DOM 順序就是把 mental model 寫進 code 的方式&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<blockquote>
<p>Tab 順序 = DOM 順序 = 使用者 mental model 的互動順序、三者該對齊。</p></blockquote>
<table>
  <thead>
      <tr>
          <th>軸</th>
          <th>由什麼決定</th>
          <th>該對齊到什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DOM 順序</td>
          <td>HTML / template 結構</td>
          <td>Mental model 的互動順序</td>
      </tr>
      <tr>
          <td>Tab 順序</td>
          <td>DOM 順序（除非 tabindex 強制覆寫）</td>
          <td>DOM 順序</td>
      </tr>
      <tr>
          <td>Mental model 順序</td>
          <td>使用者預期「先做 X 再做 Y」的流程</td>
          <td>UI 設計意圖</td>
      </tr>
  </tbody>
</table>
<p>三者偏差的後果：</p>
<ul>
<li>DOM ≠ mental model：視覺 / tab 順序跟使用者期望不一致、a11y 體驗差</li>
<li>DOM ≠ tab order（用 <code>tabindex &gt; 0</code>）：DOM 改變時 tab 順序維護成本爆炸（#52 反模式）</li>
<li>全對齊：DOM 簡單、tab 自然、a11y 預設正確</li>
</ul>
<p>要解決不對齊、<strong>優先重排 DOM</strong>、不要用 <code>tabindex</code> 強制覆寫。</p>
<hr>
<h2 id="為什麼三者該對齊到-dom-順序">為什麼三者該對齊到 DOM 順序</h2>
<h3 id="tab-順序跟-dom-順序綁定是-spec-規定">Tab 順序跟 DOM 順序綁定是 spec 規定</h3>
<p>HTML5 spec：tabbable elements 預設依 source order（DOM 順序）navigate。要改變只能用 <code>tabindex</code> 覆寫。</p>
<p><code>tabindex</code> 三種值：</p>
<table>
  <thead>
      <tr>
          <th>tabindex</th>
          <th>行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>0</code> 或不寫</td>
          <td>跟 DOM 順序、可 tab 到（依元素本身的 tabbability）</td>
      </tr>
      <tr>
          <td><code>-1</code></td>
          <td>不能 tab 到、但可被 <code>.focus()</code> 程式 focus</td>
      </tr>
      <tr>
          <td><code>&gt; 0</code>（如 <code>1</code>、<code>2</code>）</td>
          <td>強制覆寫順序、所有 <code>&gt; 0</code> 的元素先 tab、按數值升序</td>
      </tr>
  </tbody>
</table>
<p><code>tabindex &gt; 0</code> 反模式（同 <a href="../keyboard-accessibility/">#52 鍵盤可達性</a>）：</p>
<ul>
<li>全頁面只要有任何元素用 <code>tabindex &gt; 0</code>、整個 tab 順序變混亂（其他 <code>0</code> / 不寫的元素都被推到後面）</li>
<li>維護成本：DOM 改了、所有 <code>tabindex &gt; 0</code> 的數值都要重排</li>
<li>A11y：screen reader 跟視覺使用者體驗到不同順序</li>
</ul>
<p>唯一合法用法：要把元素「移出 tab cycle」用 <code>tabindex=&quot;-1&quot;</code>（例如 modal 開啟時鎖住背景）。</p>
<h3 id="mental-model-順序由-ui-設計決定">Mental model 順序由 UI 設計決定</h3>
<p>互動式 UI 隱含一個流程：使用者預期「先做 X 再做 Y」。例如：</p>
<table>
  <thead>
      <tr>
          <th>UI 類型</th>
          <th>預期 mental model 順序</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>搜尋頁</td>
          <td>1. 打 query → 2. 篩選範圍 → 3. 看結果 → 4. 載入更多</td>
      </tr>
      <tr>
          <td>表單</td>
          <td>從上到下、必填欄位先、subtmit 在最後</td>
      </tr>
      <tr>
          <td>Wizard</td>
          <td>Step 1 → Step 2 → Step 3 → Submit</td>
      </tr>
      <tr>
          <td>商品列表</td>
          <td>1. Sort / filter → 2. 看商品 → 3. 加入購物車</td>
      </tr>
      <tr>
          <td>Modal</td>
          <td>Modal 內容 → primary action → secondary action → close</td>
      </tr>
  </tbody>
</table>
<p>設計者腦中有這個順序、寫 HTML 時要把它具體化成 DOM 順序。<strong>DOM 順序就是把 mental model 寫進 code 的方式</strong>。</p>
<hr>
<h2 id="多面向常見不對齊-case">多面向：常見不對齊 case</h2>
<h3 id="面向-1filter-在-search-input-之前這次任務的-case">面向 1：Filter 在 search input 之前（這次任務的 case）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- DOM 順序：scope 先 → search input 後 --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;search-scope&#34;</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;search&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>  <span class="c">&lt;!-- pagefind input 在裡面 --&gt;</span></span></span></code></pre></div><p>Tab 順序：scope radios → search input。但 mental model 是「先打字再篩選」、Tab 應該先到 input。</p>
<p><strong>修法</strong>：DOM 重排、把 scope 移到 #search 之後。視覺位置由 CSS <code>position: absolute</code> 控制、不受 DOM 順序影響。</p>
<h3 id="面向-2submit-按鈕在-form-中間">面向 2：Submit 按鈕在 form 中間</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;email&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>送出<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>  <span class="c">&lt;!-- 太早 --&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">&lt;</span><span class="nt">textarea</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;message&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">textarea</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></span></span></code></pre></div><p>Tab 順序：email → submit → textarea。使用者打完 email 按 Enter 就送出、textarea 還沒填。</p>
<p><strong>修法</strong>：submit 移到所有 input 之後。</p>
<h3 id="面向-3logo--nav-在主要-cta-之前">面向 3：Logo / nav 在主要 CTA 之前</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/&#34;</span><span class="p">&gt;</span>Logo<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">nav</span><span class="p">&gt;</span>... 5 個 links ...<span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">&lt;</span><span class="nt">main</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">&lt;</span><span class="nt">button</span><span class="p">&gt;</span>主要 CTA<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>  <span class="c">&lt;!-- 使用者要按這個 --&gt;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span></span></span></code></pre></div><p>Tab 順序：6 個 nav links → CTA。使用者要 tab 6 次才到 CTA。</p>
<p><strong>修法</strong>：考慮加 「skip to main content」link（A11y 標準做法）— <code>&lt;a href=&quot;#main-content&quot; class=&quot;skip-link&quot;&gt;</code>。第一個 tab 就跳過 nav 到 main。</p>
<h3 id="面向-4modal-開啟時-background-仍-tabbable">面向 4：Modal 開啟時 background 仍 tabbable</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;background-content&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;...&#34;</span><span class="p">&gt;</span>某連結<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>  <span class="c">&lt;!-- 仍可 tab 到 --&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;dialog&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">&lt;</span><span class="nt">button</span><span class="p">&gt;</span>確認<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p>Tab 順序：背景連結 → modal input → confirm。使用者 tab 出 modal 跑回背景。</p>
<p><strong>修法</strong>：modal 開啟時、用 <code>inert</code> attribute（modern）或所有背景元素設 <code>tabindex=&quot;-1&quot;</code>（傳統）把它們踢出 cycle。<code>&lt;dialog&gt;</code> native 自動處理。</p>
<hr>
<h2 id="不對齊的修法優先重排-dom">不對齊的修法：優先重排 DOM</h2>
<h3 id="第一順位重排-dom">第一順位：重排 DOM</h3>
<p>把元素照 mental model 順序排在 HTML / template 裡。視覺位置如果跟 DOM 順序不同、用 CSS <code>order</code>（flex / grid）、<code>position: absolute</code>、<code>grid-template-areas</code> 控制。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- DOM 順序對齊 mental model：input → scope → drawer --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;search&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;search-scope&#34;</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">/* 視覺：scope 浮在 input 跟 drawer 之間（跟 DOM 順序無關） */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">search-shell</span> <span class="p">{</span> <span class="k">position</span><span class="p">:</span> <span class="kc">relative</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">.</span><span class="nc">search-scope</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="nb">calc</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">input</span><span class="o">-</span><span class="n">h</span><span class="p">)</span> <span class="o">+</span> <span class="mi">8</span><span class="kt">px</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>Tab 順序自然對齊 DOM、視覺位置由 CSS 獨立控制</strong> — 兩個維度解耦、不互相影響。</p>
<h3 id="第二順位js-動態移動-dom">第二順位：JS 動態移動 DOM</h3>
<p>如果元素因為 framework 限制無法 hard-coded 在對的位置（例如某 vendor library 強制 mount 點）、用 JS 在 mount 後 reparent 元素到對的位置。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// PagefindUI mount 後、把 scope 移到 input 跟 drawer 之間（如果 framework 允許）
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">scope</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.search-scope&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">drawer</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__drawer&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">drawer</span><span class="p">.</span><span class="nx">parentElement</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">scope</span><span class="p">,</span> <span class="nx">drawer</span><span class="p">);</span></span></span></code></pre></div><p>風險：framework 重渲染可能 reparent 回去（<a href="../coexisting-with-framework-managed-dom/">#5 framework-managed DOM</a>）。要驗證穩定性。</p>
<h3 id="第三順位不推薦tabindex-強制">第三順位（不推薦）：tabindex 強制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">input</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;1&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;search&#34;</span><span class="p">&gt;</span>  <span class="c">&lt;!-- 反模式：tabindex &gt; 0 --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;2&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;search-scope&#34;</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div><p>只在前兩種都做不到時用。維護成本高、a11y 跟設計工具支援差。</p>
<hr>
<h2 id="不該套用本原則的情境">不該套用本原則的情境</h2>
<p>「DOM = tab = mental model 三者對齊」原則在多數情境成立、但有合理例外：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>為什麼不該強制對齊</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>純展示頁面（無互動）</td>
          <td>沒 mental model 順序可言、預設 DOM 順序就好</td>
      </tr>
      <tr>
          <td>動態生成 list 元素</td>
          <td>List 元素數量不固定、tab order 跟著 DOM 自然走是對的</td>
      </tr>
      <tr>
          <td>模糊的 mental model</td>
          <td>當 UI 設計沒明確流程、DOM 自然順序通常已經夠用</td>
      </tr>
      <tr>
          <td>Framework 不允許重排</td>
          <td>接受次優、加 explicit hint 告知使用者</td>
      </tr>
  </tbody>
</table>
<p>四類共同特徵：<strong>沒有清楚的「使用者該先做 X 再做 Y」流程</strong> — 本原則建立在「有 mental model 可對齊」上、沒有時自然不適用。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>跟本卡的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../keyboard-accessibility/">#52 鍵盤可達性</a></td>
          <td>本卡是 #52「邏輯 tab 順序」要素的展開、含 tabindex &gt; 0 反模式詳解</td>
      </tr>
      <tr>
          <td><a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度跟意圖對齊反相關</a></td>
          <td>DOM 順序便利（先寫先 render）、mental model 對齊需要刻意設計 — 反相關</td>
      </tr>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍</a></td>
          <td>tabindex &gt; 0 是「擴張範圍」反模式 — 一個 tabindex &gt; 0 影響整頁 tab 順序</td>
      </tr>
      <tr>
          <td><a href="../native-html-over-aria-role/">#39 native HTML &gt; ARIA</a></td>
          <td>Native HTML 元素自帶正確 tab 行為、不需要 ARIA tabindex 補</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="對應的實作篇">對應的實作篇</h2>
<ul>
<li>搜尋頁 scope filter 在 search input 之前的 tab 順序問題 — Checkpoint 1 retrospective 找到（<a href="../verification-timeline-checkpoints/">#68</a> dogfooding）</li>
<li>任何「先選範圍再操作」vs「先操作再選範圍」的 UI 設計 — 都該檢視 tab order 是否對齊</li>
</ul>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫了 <code>tabindex=&quot;1&quot;</code> 或更大的數字</td>
          <td>換重排 DOM、避免 tabindex &gt; 0</td>
      </tr>
      <tr>
          <td>Tab 順序跟「使用者會先做什麼」感覺反</td>
          <td>列 mental model 流程、檢查 DOM 順序</td>
      </tr>
      <tr>
          <td>做 a11y review 才發現 tab 順序怪</td>
          <td>Checkpoint 1 沒列鍵盤使用 case、補進開工前清單</td>
      </tr>
      <tr>
          <td>用 JS reparent 元素改順序、framework 改回來</td>
          <td>重新評估架構、把元素放在 framework 邊界外</td>
      </tr>
      <tr>
          <td>內心 OS：「視覺位置是 X、所以 DOM 也該在 X」</td>
          <td>視覺跟 DOM 解耦才是對的設計</td>
      </tr>
      <tr>
          <td>看到 <code>tabindex=&quot;-1&quot;</code> 在不該被 tab 的元素上</td>
          <td>合理使用（modal 背景 / 先 focus 後 reveal）</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：DOM 順序是寫進 code 的 mental model、tab 順序是使用者體驗的 mental model — 兩者該由「重排 DOM」對齊、不該由「tabindex」強制。視覺位置跟 DOM 順序解耦（用 CSS 控制）、讓兩者各自獨立優化。</p>
]]></content:encoded></item></channel></rss>