<?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>A11y on Tarragon</title><link>https://tarrragon.github.io/blog/tags/a11y/</link><description>Recent content in A11y 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/a11y/index.xml" rel="self" type="application/rss+xml"/><item><title>Accessibility and Focus — A11y 三道防線</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/</guid><description>&lt;p>A11y 三道防線：靜態（鍵盤可達性三要素）、動態（focus 跟 aria-live）、優先 Native HTML &amp;gt; ARIA。鍵盤 / 視覺 / motor / 認知都納入。&lt;/p>
&lt;p>適用：寫互動 UI、JS reparent / hide 元素、自製 component（modal / dropdown / tabs）、客製外部組件後檢查 a11y。
不適用：純後端 / 純資料流（沒有使用者直接互動）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋鍵盤可達性三要素、focus management 模板、aria-live 設計、native HTML 優先原則。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>自製 modal / dropdown / tabs / accordion&lt;/td>
 &lt;td>先看有沒有 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> / &lt;code>&amp;lt;details&amp;gt;&lt;/code> 能用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JS reparent 或 hide 元素&lt;/td>
 &lt;td>保存 focus、操作後還原&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>動態變動內容（搜尋結果、filter 切換、status 訊息）&lt;/td>
 &lt;td>加 &lt;code>aria-live&lt;/code> region&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者反映「鍵盤跑掉」「Tab 順序怪」&lt;/td>
 &lt;td>檢查 visible focus indicator + tab order&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>role=&amp;quot;button&amp;quot;&lt;/code> &lt;code>role=&amp;quot;dialog&amp;quot;&lt;/code> 等 ARIA role&lt;/td>
 &lt;td>停 — 看 native HTML 能不能用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>行動裝置誤點&lt;/td>
 &lt;td>檢查 hit target 大小（最小 44×44 px）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-a11y-是預設不是補丁">為什麼 a11y 是預設不是補丁&lt;/h2>
&lt;p>A11y 不是「完整功能後再加上」、是&lt;strong>設計時就決定的結構&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用 &lt;code>&amp;lt;button&amp;gt;&lt;/code> vs &lt;code>&amp;lt;div onclick&amp;gt;&lt;/code> → 鍵盤 / focus / a11y tree 自帶 vs 全部要自己補&lt;/li>
&lt;li>modal 用 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> vs 自己組 → focus trap / escape / scrollable / inert 自帶 vs 全部要自己補&lt;/li>
&lt;li>動態內容變動有 aria-live vs 沒 → screen reader 知道 vs 不知道&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>事後補 a11y 比事前設計貴 5-10 倍&lt;/strong>。寫之前先選對結構、後續成本低。&lt;/p>
&lt;hr>
&lt;h2 id="防線-1靜態鍵盤可達性三要素">防線 1：靜態鍵盤可達性三要素&lt;/h2>
&lt;p>鍵盤使用者要能用、三個元素缺一不可：&lt;/p>
&lt;h3 id="要素-1visible-focus-indicator">要素 1：Visible focus indicator&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c">/* 反例：去掉預設 focus outline */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">focus&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">outline&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c">/* 對例：可見的 focus indicator */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">focus-visible&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">outline&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="nf">var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="n">focus&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="kc">color&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">outline-offset&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>:focus-visible&lt;/code>（鍵盤 focus）跟 &lt;code>:focus&lt;/code>（含滑鼠 click 後）區分 — 滑鼠使用者不需要看到 outline、鍵盤使用者必須看到。&lt;/p>
&lt;h3 id="要素-2邏輯-tab-順序">要素 2：邏輯 Tab 順序&lt;/h3>
&lt;p>Tab 順序預設由 DOM tree 決定。如果視覺順序跟 DOM 順序不同（例如用 CSS grid 重排），考慮：&lt;/p>
&lt;ul>
&lt;li>重排 DOM 順序對齊視覺&lt;/li>
&lt;li>用 &lt;code>tabindex=&amp;quot;0&amp;quot;&lt;/code> 讓元素可 focus（不要用 &amp;gt; 0）&lt;/li>
&lt;li>不要用 &lt;code>tabindex=&amp;quot;-1&amp;quot;&lt;/code> 跳過該 focus 的元素&lt;/li>
&lt;/ul>
&lt;h3 id="要素-3modal--drawer-有-escape-路徑">要素 3：Modal / drawer 有 escape 路徑&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">dialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;keydown&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;Escape&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">dialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或用 &lt;code>&amp;lt;dialog&amp;gt;&lt;/code> native — &lt;code>Escape&lt;/code> 自帶。&lt;/p></description><content:encoded><![CDATA[<p>A11y 三道防線：靜態（鍵盤可達性三要素）、動態（focus 跟 aria-live）、優先 Native HTML &gt; ARIA。鍵盤 / 視覺 / motor / 認知都納入。</p>
<p>適用：寫互動 UI、JS reparent / hide 元素、自製 component（modal / dropdown / tabs）、客製外部組件後檢查 a11y。
不適用：純後端 / 純資料流（沒有使用者直接互動）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋鍵盤可達性三要素、focus management 模板、aria-live 設計、native HTML 優先原則。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自製 modal / dropdown / tabs / accordion</td>
          <td>先看有沒有 <code>&lt;dialog&gt;</code> / <code>&lt;details&gt;</code> 能用</td>
      </tr>
      <tr>
          <td>JS reparent 或 hide 元素</td>
          <td>保存 focus、操作後還原</td>
      </tr>
      <tr>
          <td>動態變動內容（搜尋結果、filter 切換、status 訊息）</td>
          <td>加 <code>aria-live</code> region</td>
      </tr>
      <tr>
          <td>使用者反映「鍵盤跑掉」「Tab 順序怪」</td>
          <td>檢查 visible focus indicator + tab order</td>
      </tr>
      <tr>
          <td>即將寫 <code>role=&quot;button&quot;</code> <code>role=&quot;dialog&quot;</code> 等 ARIA role</td>
          <td>停 — 看 native HTML 能不能用</td>
      </tr>
      <tr>
          <td>行動裝置誤點</td>
          <td>檢查 hit target 大小（最小 44×44 px）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-a11y-是預設不是補丁">為什麼 a11y 是預設不是補丁</h2>
<p>A11y 不是「完整功能後再加上」、是<strong>設計時就決定的結構</strong>：</p>
<ul>
<li>用 <code>&lt;button&gt;</code> vs <code>&lt;div onclick&gt;</code> → 鍵盤 / focus / a11y tree 自帶 vs 全部要自己補</li>
<li>modal 用 <code>&lt;dialog&gt;</code> vs 自己組 → focus trap / escape / scrollable / inert 自帶 vs 全部要自己補</li>
<li>動態內容變動有 aria-live vs 沒 → screen reader 知道 vs 不知道</li>
</ul>
<p><strong>事後補 a11y 比事前設計貴 5-10 倍</strong>。寫之前先選對結構、後續成本低。</p>
<hr>
<h2 id="防線-1靜態鍵盤可達性三要素">防線 1：靜態鍵盤可達性三要素</h2>
<p>鍵盤使用者要能用、三個元素缺一不可：</p>
<h3 id="要素-1visible-focus-indicator">要素 1：Visible focus indicator</h3>





<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">/* 反例：去掉預設 focus outline */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">focus</span> <span class="p">{</span> <span class="k">outline</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c">/* 對例：可見的 focus indicator */</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">focus-visible</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="k">outline</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span> <span class="kc">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">focus</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="k">outline-offset</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>:focus-visible</code>（鍵盤 focus）跟 <code>:focus</code>（含滑鼠 click 後）區分 — 滑鼠使用者不需要看到 outline、鍵盤使用者必須看到。</p>
<h3 id="要素-2邏輯-tab-順序">要素 2：邏輯 Tab 順序</h3>
<p>Tab 順序預設由 DOM tree 決定。如果視覺順序跟 DOM 順序不同（例如用 CSS grid 重排），考慮：</p>
<ul>
<li>重排 DOM 順序對齊視覺</li>
<li>用 <code>tabindex=&quot;0&quot;</code> 讓元素可 focus（不要用 &gt; 0）</li>
<li>不要用 <code>tabindex=&quot;-1&quot;</code> 跳過該 focus 的元素</li>
</ul>
<h3 id="要素-3modal--drawer-有-escape-路徑">要素 3：Modal / drawer 有 escape 路徑</h3>





<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="nx">dialog</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;Escape&#39;</span><span class="p">)</span> <span class="nx">dialog</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>或用 <code>&lt;dialog&gt;</code> native — <code>Escape</code> 自帶。</p>
<hr>
<h2 id="防線-2動態-a11y">防線 2：動態 a11y</h2>
<h3 id="focus-management-on-dom-move">Focus management on DOM move</h3>
<p>JS reparent / hide 元素時、focus 會跑掉（落到 body）。需要保存與還原：</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="kd">function</span> <span class="nx">moveFilter</span><span class="p">(</span><span class="nx">targetSlot</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="kr">const</span> <span class="nx">filter</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;.filter&#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">focused</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">activeElement</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="kr">const</span> <span class="nx">wasFilterFocused</span> <span class="o">=</span> <span class="nx">filter</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="nx">focused</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="nx">targetSlot</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>  <span class="c1">// reparent
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">wasFilterFocused</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">focused</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>  <span class="c1">// 還原 focus
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="aria-live-廣播動態變動">aria-live 廣播動態變動</h3>
<p>Screen reader 預設不會朗讀「DOM 變動」、要明確告訴它：</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;!-- polite：等使用者操作完才朗讀（搜尋結果數量、filter 切換） --&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">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#34;</span> <span class="na">aria-atomic</span><span class="o">=</span><span class="s">&#34;true&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  顯示 12 筆結果
</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="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c">&lt;!-- assertive：立刻打斷朗讀（錯誤訊息、緊急狀態） --&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="na">aria-live</span><span class="o">=</span><span class="s">&#34;assertive&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;alert&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  搜尋失敗、請重試
</span></span><span class="line"><span class="ln">9</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><code>aria-atomic=&quot;true&quot;</code> 整段重讀（不只朗讀變動的部分）。</p>
<h3 id="範例搜尋結果區">範例：搜尋結果區</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;results&#34;</span> <span class="na">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#34;</span> <span class="na">aria-atomic</span><span class="o">=</span><span class="s">&#34;false&#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">p</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;status&#34;</span><span class="p">&gt;</span>顯示 <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;count&#34;</span><span class="p">&gt;</span>12<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> 筆結果<span class="p">&lt;/</span><span class="nt">p</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">ul</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">ul</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="p">&gt;</span></span></span></code></pre></div><p>JS 更新 <code>#count</code> 的 textContent 時、screen reader 朗讀「顯示 12 筆結果」。</p>
<hr>
<h2 id="防線-3native-html--aria">防線 3：Native HTML &gt; ARIA</h2>
<h3 id="為什麼優先-native">為什麼優先 Native</h3>
<table>
  <thead>
      <tr>
          <th>元素</th>
          <th>Native 自帶</th>
          <th>ARIA 補強需要</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>&lt;button&gt;</code></td>
          <td>Tab focus、Enter/Space 觸發、a11y role、disabled 狀態</td>
          <td><code>role=&quot;button&quot;</code> + tabindex + keydown listener + aria-disabled</td>
      </tr>
      <tr>
          <td><code>&lt;dialog&gt;</code></td>
          <td>Modal focus trap、Escape 關閉、<code>::backdrop</code>、<code>inert</code> 外層</td>
          <td><code>role=&quot;dialog&quot;</code> + aria-modal + 自寫 focus trap + Escape handler + inert polyfill</td>
      </tr>
      <tr>
          <td><code>&lt;details&gt;</code></td>
          <td>Toggle 展開、鍵盤、a11y</td>
          <td><code>role=&quot;region&quot;</code> + aria-expanded + 自寫 click handler + keyboard support</td>
      </tr>
      <tr>
          <td><code>&lt;fieldset&gt;+&lt;legend&gt;</code></td>
          <td>群組 a11y、screen reader 讀 legend</td>
          <td><code>role=&quot;radiogroup&quot;</code> + aria-labelledby</td>
      </tr>
      <tr>
          <td><code>&lt;input type=&quot;...&quot;&gt;</code></td>
          <td>各種 input 的 native UX、validation、a11y</td>
          <td>全部自寫</td>
      </tr>
  </tbody>
</table>
<h3 id="何時用-aria">何時用 ARIA</h3>
<p>ARIA 是補強、不是替代：</p>
<ul>
<li>用 native 但 a11y tree 還不夠（標 aria-label / aria-describedby 補語意）</li>
<li>真的沒有 native 元素（complex composite widget、tabs、tree）</li>
<li>動態變動需要廣播（aria-live）</li>
</ul>
<h3 id="範例自製-toggle-還是-native-checkbox">範例：自製 toggle 還是 native checkbox</h3>
<p><strong>錯</strong>：</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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toggle&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;switch&#34;</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;0&#34;</span> <span class="na">aria-checked</span><span class="o">=</span><span class="s">&#34;false&#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">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;track&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</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="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">toggle</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="p">...);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">toggle</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;keydown&#39;</span><span class="p">,</span> <span class="nx">e</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;Enter&#39;</span> <span class="o">||</span> <span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39; &#39;</span><span class="p">)</span> <span class="p">...;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p><strong>對</strong>：</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="p">&lt;</span><span class="nt">label</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toggle&#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">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;checkbox&#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">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;track&#34;</span> <span class="na">aria-hidden</span><span class="o">=</span><span class="s">&#34;true&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</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">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;visually-hidden&#34;</span><span class="p">&gt;</span>啟用 dark mode<span class="p">&lt;/</span><span class="nt">span</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">label</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="p">.</span><span class="nc">toggle</span> <span class="nt">input</span> <span class="p">{</span> <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span> <span class="k">opacity</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">.</span><span class="nc">toggle</span> <span class="nt">input</span><span class="p">:</span><span class="nd">checked</span> <span class="o">+</span> <span class="p">.</span><span class="nc">track</span> <span class="p">{</span> <span class="k">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">brand</span><span class="p">);</span> <span class="p">}</span></span></span></code></pre></div><p>Native checkbox 自帶 keyboard / focus / state、CSS 把它隱藏、視覺用 <code>.track</code> 呈現。</p>
<hr>
<h2 id="視覺--motor-a11y">視覺 / Motor a11y</h2>
<h3 id="視覺輔助">視覺輔助</h3>





<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">/* 對比度 */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">:</span><span class="nd">root</span> <span class="p">{</span> <span class="nv">--text</span><span class="p">:</span> <span class="mh">#1a202c</span><span class="p">;</span> <span class="nv">--bg</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c">/* WCAG AA: 普通文字 4.5:1、大文字 3:1 */</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c">/* 字型放大時不破版 */</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">.</span><span class="nc">container</span> <span class="p">{</span> <span class="k">max-width</span><span class="p">:</span> <span class="mi">60</span><span class="kt">ch</span><span class="p">;</span> <span class="p">}</span>  <span class="c">/* ch 跟字型同步 */</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">.</span><span class="nc">text</span> <span class="p">{</span> <span class="k">font-size</span><span class="p">:</span> <span class="mi">1</span><span class="kt">rem</span><span class="p">;</span> <span class="k">line-height</span><span class="p">:</span> <span class="mf">1.6</span><span class="p">;</span> <span class="p">}</span>  <span class="c">/* rem 跟使用者設定同步 */</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c">/* prefers-reduced-motion */</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">prefers-reduced-motion</span><span class="o">:</span> <span class="nt">reduce</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="o">*</span> <span class="p">{</span> <span class="k">animation-duration</span><span class="p">:</span> <span class="mf">0.01</span><span class="kt">ms</span> <span class="cp">!important</span><span class="p">;</span> <span class="k">transition-duration</span><span class="p">:</span> <span class="mf">0.01</span><span class="kt">ms</span> <span class="cp">!important</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="motor--hit-target">Motor / Hit target</h3>





<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">/* 觸控 hit target 最小 44×44 px (WCAG AAA) */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nt">button</span><span class="o">,</span> <span class="nt">a</span><span class="o">,</span> <span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">&#34;button&#34;</span><span class="o">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">min-height</span><span class="p">:</span> <span class="mi">44</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">min-width</span><span class="p">:</span> <span class="mi">44</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c">/* 兩個 hit target 之間留 8px+ 間距、避免誤點 */</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">.</span><span class="nc">toolbar</span> <span class="o">&gt;</span> <span class="o">*</span> <span class="o">+</span> <span class="o">*</span> <span class="p">{</span> <span class="k">margin-left</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1自製-dropdown">範例 1：自製 dropdown</h3>
<p><strong>錯</strong>：</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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;dropdown&#34;</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;0&#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">span</span><span class="p">&gt;</span>選單<span class="p">&lt;/</span><span class="nt">span</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;menu&#34;</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">class</span><span class="o">=</span><span class="s">&#34;item&#34;</span><span class="p">&gt;</span>選項 1<span class="p">&lt;/</span><span class="nt">div</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">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;item&#34;</span><span class="p">&gt;</span>選項 2<span class="p">&lt;/</span><span class="nt">div</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">div</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>問題：no native focus、no keyboard、no a11y role、screen reader 不知道是 menu。</p>
<p><strong>對</strong>：</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="p">&lt;</span><span class="nt">button</span> <span class="na">aria-haspopup</span><span class="o">=</span><span class="s">&#34;menu&#34;</span> <span class="na">aria-expanded</span><span class="o">=</span><span class="s">&#34;false&#34;</span> <span class="na">aria-controls</span><span class="o">=</span><span class="s">&#34;menu1&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  選單
</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="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">&lt;</span><span class="nt">ul</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;menu1&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menu&#34;</span> <span class="na">hidden</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">li</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menuitem&#34;</span><span class="p">&gt;&lt;</span><span class="nt">button</span><span class="p">&gt;</span>選項 1<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;&lt;/</span><span class="nt">li</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">li</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;menuitem&#34;</span><span class="p">&gt;&lt;</span><span class="nt">button</span><span class="p">&gt;</span>選項 2<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;&lt;/</span><span class="nt">li</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">ul</span><span class="p">&gt;</span></span></span></code></pre></div><p>或如果是「選擇一個」 → <code>&lt;select&gt;</code> native。</p>
<h3 id="範例-2filter-切換沒-a11y-broadcast">範例 2：filter 切換沒 a11y broadcast</h3>
<p><strong>錯</strong>：</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="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.result&#39;</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">tag</span> <span class="o">===</span> <span class="nx">currentFilter</span> <span class="o">?</span> <span class="s1">&#39;block&#39;</span> <span class="o">:</span> <span class="s1">&#39;none&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// screen reader 不知道結果變了
</span></span></span></code></pre></div><p><strong>對</strong>：</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="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;results&#34;</span> <span class="na">aria-live</span><span class="o">=</span><span class="s">&#34;polite&#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">p</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;status&#34;</span><span class="p">&gt;</span>顯示 <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;count&#34;</span><span class="p">&gt;</span>12<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> 筆結果（filter: <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;filter&#34;</span><span class="p">&gt;</span>全部<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>）<span class="p">&lt;/</span><span class="nt">p</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="p">&gt;</span></span></span></code></pre></div>




<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="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// ... filter logic
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;count&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">visibleCount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">).</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">currentFilter</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="c1">// aria-live 自動朗讀
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h3 id="範例-3js-移動元素-focus-跑掉">範例 3：JS 移動元素 focus 跑掉</h3>
<p><strong>錯</strong>：</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">// resize 時把 filter 從 mobile drawer 移到 desktop sidebar
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">matches</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">drawer</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1">// 如果 filter 內的某個 input 有 focus、reparent 後 focus 落到 body
</span></span></span></code></pre></div><p><strong>對</strong>：</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="nx">mediaQuery</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="kr">const</span> <span class="nx">focused</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">activeElement</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">wasInFilter</span> <span class="o">=</span> <span class="nx">filter</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="nx">focused</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">mediaQuery</span><span class="p">.</span><span class="nx">matches</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">sidebar</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">drawer</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">filter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">wasInFilter</span><span class="p">)</span> <span class="nx">focused</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>  <span class="c1">// 還原 focus
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫互動 UI 時：</p>
<ul>
<li><input disabled="" type="checkbox"> 用 <code>&lt;button&gt;</code> <code>&lt;dialog&gt;</code> <code>&lt;details&gt;</code> <code>&lt;fieldset&gt;</code> 取代自製 ARIA 結構？</li>
<li><input disabled="" type="checkbox"> visible focus indicator 沒被 <code>outline: none</code> 拿掉？</li>
<li><input disabled="" type="checkbox"> Tab 順序符合視覺順序（沒用 <code>tabindex &gt; 0</code>）？</li>
<li><input disabled="" type="checkbox"> Modal / drawer 有 Escape 關閉路徑？</li>
<li><input disabled="" type="checkbox"> JS reparent / hide 時保存與還原 focus？</li>
<li><input disabled="" type="checkbox"> 動態變動內容用 <code>aria-live</code> 廣播？</li>
<li><input disabled="" type="checkbox"> 對比度 ≥ 4.5:1（普通文字）？</li>
<li><input disabled="" type="checkbox"> Hit target ≥ 44×44 px？</li>
<li><input disabled="" type="checkbox"> <code>prefers-reduced-motion</code> 時關掉動畫？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/focus-management-on-dom-move/" data-link-title="動態 DOM 移動時的 focus 管理" data-link-desc="Filter slot 跨 viewport 搬節點、scope filter 隱藏結果 — 這類 DOM 變動會讓鍵盤 focus 跑掉或停在不可見位置。本文盤點動態 DOM 對 focus 的影響與檢查方法。">focus-management-on-dom-move</a> — 動態 DOM 移動時的 focus 管理</li>
<li><a href="/blog/report/aria-live-for-dynamic-content/" data-link-title="Screen reader 與動態內容變動的 live region 設計" data-link-desc="Scope filter 切換、結果數量變動 — screen reader 使用者看不到視覺變動、需要 aria-live region 主動朗讀。本文盤點 live region 的設計選擇與適用情境。">aria-live-for-dynamic-content</a> — Screen reader 與動態內容變動的 live region 設計</li>
<li><a href="/blog/report/native-html-over-aria-role/" data-link-title="Native HTML element 優先於 ARIA role 的取捨" data-link-desc="用 `&lt;fieldset&gt;&lt;legend&gt;` 比 `&lt;div role=&#34;radiogroup&#34;&gt;` 安全、用 `&lt;button&gt;` 比 `&lt;div role=&#34;button&#34;&gt;` 直接 — native element 自帶完整無障礙語意與行為。本文盤點 ARIA role 是 fallback、不是 default。">native-html-over-aria-role</a> — Native HTML element 優先於 ARIA role 的取捨</li>
<li><a href="/blog/report/keyboard-accessibility/" data-link-title="鍵盤可達性：focus indicator、tab 順序、escape 路徑" data-link-desc="鍵盤使用者用 tab / shift&#43;tab 導航、enter / space 激活、esc 退出。三件事決定可不可用：focus 是否可見、tab 順序是否合理、modal / overlay 有沒有 escape 路徑。本文盤點搜尋頁的鍵盤 a11y 風險點。">keyboard-accessibility</a> — 鍵盤可達性：focus indicator、tab 順序、escape 路徑</li>
<li><a href="/blog/report/motor-accessibility-hit-target/" data-link-title="Motor 可達性：hit target、間距、誤點防護" data-link-desc="Hit target 太小會讓行動裝置使用者誤點、motor 障礙使用者更甚。WCAG AAA 建議 ≥ 44×44px、間距足夠避免誤觸。本文展開 hit target 設計與相關 motor a11y 風險點。">motor-accessibility-hit-target</a> — Motor 可達性：hit target、間距、誤點防護</li>
<li><a href="/blog/report/visual-aids-contrast-zoom-responsive/" data-link-title="視覺輔助：對比度、放大、字型 zoom 的 layout 適配" data-link-desc="色弱、低對比敏感、低視力使用者跟一般使用者「看到的不是同一個 UI」 — 對比度足夠嗎、絕對定位元件在放大模式下是否可達、字型放大 200% 後 layout 還好嗎。本文盤點視覺呈現面的 a11y 風險點。">visual-aids-contrast-zoom-responsive</a> — 視覺輔助：對比度、放大、字型 zoom 的 layout 適配</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item></channel></rss>