[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"post-\u002Fblog\u002Faccessibility-audit-fixes":3},{"id":4,"title":5,"body":6,"category":861,"date":862,"description":863,"draft":864,"extension":865,"meta":866,"navigation":143,"path":867,"readingTime":868,"seo":869,"stem":870,"tags":871,"__hash__":875},"blog\u002Fblog\u002Faccessibility-audit-fixes.md","從退件到通過：網站無障礙檢測的修正紀錄",{"type":7,"value":8,"toc":844},"minimark",[9,13,25,29,33,44,79,82,85,203,206,209,224,277,283,294,297,300,320,386,395,398,401,460,463,466,469,528,531,538,541,544,548,551,624,667,674,709,713,720,764,770,811,814,817,820,823,840],[10,11,12],"p",{},"這是我做過最長的一次修正：一個 Vue 3 + Vuetify 的網站要送無障礙檢測。專案前期已經用 FreeGo 自動檢測並修正過幾輪，我接手的是人工檢測結果和後續的退件修正，中間經歷多次退件，才取得 A 等級標章。",[10,14,15,16,20,21,24],{},"一開始我以為無障礙就是補上 ",[17,18,19],"code",{},"alt"," 和 ",[17,22,23],{},"aria-label","。實際做完才發現，它考驗的是整個網站的結構，而 FreeGo 這類自動檢測工具通過，不代表人工檢測會過。這篇依問題類型整理我修了什麼。",[26,27,28],"h2",{"id":28},"語意和基本資訊",[30,31,32],"h3",{"id":32},"頁面語言",[10,34,35,36,39,40,43],{},"螢幕報讀軟體會依 ",[17,37,38],{},"lang"," 決定用什麼語言朗讀，專案預設的 ",[17,41,42],{},"en"," 會讓中文內容被用錯誤的方式處理：",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-html shiki shiki-themes github-dark","\u003Chtml lang=\"zh-Hant\">\n","html","",[17,52,53],{"__ignoreMap":50},[54,55,58,62,65,69,72,76],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"s95oV","\u003C",[54,63,49],{"class":64},"s4JwU",[54,66,68],{"class":67},"svObZ"," lang",[54,70,71],{"class":60},"=",[54,73,75],{"class":74},"sU2Wk","\"zh-Hant\"",[54,77,78],{"class":60},">\n",[30,80,81],{"id":81},"網頁標題要跟著頁面變",[10,83,84],{},"單頁應用切換路由時，標題不會自己改變。使用者聽不出換了頁面，所以我在路由設定標題，再統一更新：",[45,86,90],{"className":87,"code":88,"language":89,"meta":50,"style":50},"language-js shiki shiki-themes github-dark","{\n  path: '\u002Fmember-login',\n  meta: { title: '會員中心 > 會員登入' },\n}\n\nrouter.afterEach((to) => {\n  document.title = `${to.meta.title} | 網站名稱`\n})\n","js",[17,91,92,97,112,132,138,145,171,197],{"__ignoreMap":50},[54,93,94],{"class":56,"line":57},[54,95,96],{"class":60},"{\n",[54,98,100,103,106,109],{"class":56,"line":99},2,[54,101,102],{"class":67},"  path",[54,104,105],{"class":60},": ",[54,107,108],{"class":74},"'\u002Fmember-login'",[54,110,111],{"class":60},",\n",[54,113,115,118,121,124,126,129],{"class":56,"line":114},3,[54,116,117],{"class":67},"  meta",[54,119,120],{"class":60},": { ",[54,122,123],{"class":67},"title",[54,125,105],{"class":60},[54,127,128],{"class":74},"'會員中心 > 會員登入'",[54,130,131],{"class":60}," },\n",[54,133,135],{"class":56,"line":134},4,[54,136,137],{"class":60},"}\n",[54,139,141],{"class":56,"line":140},5,[54,142,144],{"emptyLinePlaceholder":143},true,"\n",[54,146,148,151,154,157,161,164,168],{"class":56,"line":147},6,[54,149,150],{"class":60},"router.",[54,152,153],{"class":67},"afterEach",[54,155,156],{"class":60},"((",[54,158,160],{"class":159},"s9osk","to",[54,162,163],{"class":60},") ",[54,165,167],{"class":166},"snl16","=>",[54,169,170],{"class":60}," {\n",[54,172,174,177,179,182,184,187,190,192,194],{"class":56,"line":173},7,[54,175,176],{"class":60},"  document.title ",[54,178,71],{"class":166},[54,180,181],{"class":74}," `${",[54,183,160],{"class":60},[54,185,186],{"class":74},".",[54,188,189],{"class":60},"meta",[54,191,186],{"class":74},[54,193,123],{"class":60},[54,195,196],{"class":74},"} | 網站名稱`\n",[54,198,200],{"class":56,"line":199},8,[54,201,202],{"class":60},"})\n",[30,204,205],{"id":205},"標題階層",[10,207,208],{},"這是退件次數最多的項目。檢測看的是文件大綱，不是字體大小。",[10,210,211,212,215,216,219,220,223],{},"Vuetify 的 ",[17,213,214],{},"text-h3"," 只是樣式，不會變成真的標題。很多畫面看起來是標題，其實是 ",[17,217,218],{},"div","；也有為了字大而直接用 ",[17,221,222],{},"\u003Ch1>"," 的地方。修正時要把視覺和語意分開：",[45,225,227],{"className":47,"code":226,"language":49,"meta":50,"style":50},"\u003C!-- 語意由標籤決定，大小由 class 決定 -->\n\u003Ch1 class=\"text-h5\">會員中心\u003C\u002Fh1>\n\u003Ch2 class=\"text-h3\">會員登入\u003C\u002Fh2>\n",[17,228,229,235,257],{"__ignoreMap":50},[54,230,231],{"class":56,"line":57},[54,232,234],{"class":233},"sAwPA","\u003C!-- 語意由標籤決定，大小由 class 決定 -->\n",[54,236,237,239,242,245,247,250,253,255],{"class":56,"line":99},[54,238,61],{"class":60},[54,240,241],{"class":64},"h1",[54,243,244],{"class":67}," class",[54,246,71],{"class":60},[54,248,249],{"class":74},"\"text-h5\"",[54,251,252],{"class":60},">會員中心\u003C\u002F",[54,254,241],{"class":64},[54,256,78],{"class":60},[54,258,259,261,263,265,267,270,273,275],{"class":56,"line":114},[54,260,61],{"class":60},[54,262,26],{"class":64},[54,264,244],{"class":67},[54,266,71],{"class":60},[54,268,269],{"class":74},"\"text-h3\"",[54,271,272],{"class":60},">會員登入\u003C\u002F",[54,274,26],{"class":64},[54,276,78],{"class":60},[10,278,279,280,282],{},"原則是每頁一個 ",[17,281,241],{},"，階層不能跳級。",[10,284,285,286,289,290,293],{},"另外我也移除了 ",[17,287,288],{},"\u003Ch1 role=\"alert\">"," 這種寫法。",[17,291,292],{},"alert"," 會讓報讀軟體立刻打斷使用者，不適合用在一般標題上。",[26,295,296],{"id":296},"替代文字",[10,298,299],{},"替代文字不是每張圖都要寫：",[301,302,303,311],"ul",{},[304,305,306,310],"li",{},[307,308,309],"strong",{},"有資訊的圖片","：描述內容。",[304,312,313,316,317,319],{},[307,314,315],{},"純裝飾的圖片","：",[17,318,19],{}," 設成空字串，報讀軟體就會跳過。",[45,321,323],{"className":47,"code":322,"language":49,"meta":50,"style":50},"\u003C!-- 之前：報讀軟體會念出沒有意義的內容 -->\n\u003Cv-img alt=\"登入頁插圖\" src=\"login.svg\" \u002F>\n\n\u003C!-- 之後：裝飾圖片不打擾使用者 -->\n\u003Cv-img alt=\"\" src=\"login.svg\" \u002F>\n",[17,324,325,330,356,360,365],{"__ignoreMap":50},[54,326,327],{"class":56,"line":57},[54,328,329],{"class":233},"\u003C!-- 之前：報讀軟體會念出沒有意義的內容 -->\n",[54,331,332,334,337,340,342,345,348,350,353],{"class":56,"line":99},[54,333,61],{"class":60},[54,335,336],{"class":64},"v-img",[54,338,339],{"class":67}," alt",[54,341,71],{"class":60},[54,343,344],{"class":74},"\"登入頁插圖\"",[54,346,347],{"class":67}," src",[54,349,71],{"class":60},[54,351,352],{"class":74},"\"login.svg\"",[54,354,355],{"class":60}," \u002F>\n",[54,357,358],{"class":56,"line":114},[54,359,144],{"emptyLinePlaceholder":143},[54,361,362],{"class":56,"line":134},[54,363,364],{"class":233},"\u003C!-- 之後：裝飾圖片不打擾使用者 -->\n",[54,366,367,369,371,373,375,378,380,382,384],{"class":56,"line":140},[54,368,61],{"class":60},[54,370,336],{"class":64},[54,372,339],{"class":67},[54,374,71],{"class":60},[54,376,377],{"class":74},"\"\"",[54,379,347],{"class":67},[54,381,71],{"class":60},[54,383,352],{"class":74},[54,385,355],{"class":60},[10,387,388,389,20,391,394],{},"這點和直覺相反。不寫 ",[17,390,19],{},[17,392,393],{},"alt=\"\""," 不一樣，前者可能會念出檔名。",[26,396,397],{"id":397},"連結和按鈕要說清楚目的",[10,399,400],{},"「下載」、「\u003C」、「>」這類文字，單獨聽完全不知道作用。按鈕需要能單獨理解的名稱：",[45,402,404],{"className":47,"code":403,"language":49,"meta":50,"style":50},"\u003Cv-btn icon=\"mdi-chevron-left\" aria-label=\"上一項說明\" \u002F>\n\u003Ca href=\"manual.pdf\" aria-label=\"下載手機版操作手冊 PDF\">下載\u003C\u002Fa>\n",[17,405,406,431],{"__ignoreMap":50},[54,407,408,410,413,416,418,421,424,426,429],{"class":56,"line":57},[54,409,61],{"class":60},[54,411,412],{"class":64},"v-btn",[54,414,415],{"class":67}," icon",[54,417,71],{"class":60},[54,419,420],{"class":74},"\"mdi-chevron-left\"",[54,422,423],{"class":67}," aria-label",[54,425,71],{"class":60},[54,427,428],{"class":74},"\"上一項說明\"",[54,430,355],{"class":60},[54,432,433,435,438,441,443,446,448,450,453,456,458],{"class":56,"line":99},[54,434,61],{"class":60},[54,436,437],{"class":64},"a",[54,439,440],{"class":67}," href",[54,442,71],{"class":60},[54,444,445],{"class":74},"\"manual.pdf\"",[54,447,423],{"class":67},[54,449,71],{"class":60},[54,451,452],{"class":74},"\"下載手機版操作手冊 PDF\"",[54,454,455],{"class":60},">下載\u003C\u002F",[54,457,437],{"class":64},[54,459,78],{"class":60},[26,461,462],{"id":462},"表單",[30,464,465],{"id":465},"必填要說出來",[10,467,468],{},"只用紅色星號，報讀軟體不一定念得出來，所以我把「必填」寫進標籤文字：",[45,470,472],{"className":47,"code":471,"language":49,"meta":50,"style":50},"\u003Cv-text-field>\n  \u003Ctemplate #label>\n    \u003Cspan>驗證碼(必填)\u003C\u002Fspan>\n  \u003C\u002Ftemplate>\n\u003C\u002Fv-text-field>\n",[17,473,474,483,496,510,519],{"__ignoreMap":50},[54,475,476,478,481],{"class":56,"line":57},[54,477,61],{"class":60},[54,479,480],{"class":64},"v-text-field",[54,482,78],{"class":60},[54,484,485,488,491,494],{"class":56,"line":99},[54,486,487],{"class":60},"  \u003C",[54,489,490],{"class":64},"template",[54,492,493],{"class":67}," #label",[54,495,78],{"class":60},[54,497,498,501,503,506,508],{"class":56,"line":114},[54,499,500],{"class":60},"    \u003C",[54,502,54],{"class":64},[54,504,505],{"class":60},">驗證碼(必填)\u003C\u002F",[54,507,54],{"class":64},[54,509,78],{"class":60},[54,511,512,515,517],{"class":56,"line":134},[54,513,514],{"class":60},"  \u003C\u002F",[54,516,490],{"class":64},[54,518,78],{"class":60},[54,520,521,524,526],{"class":56,"line":140},[54,522,523],{"class":60},"\u003C\u002F",[54,525,480],{"class":64},[54,527,78],{"class":60},[30,529,530],{"id":530},"標籤要和欄位關聯",[10,532,533,534,537],{},"每個欄位都要有對應的 ",[17,535,536],{},"label","。像驗證碼這類為了版面省略標籤的欄位，就很容易被抓出來。",[30,539,540],{"id":540},"日期欄位要能用鍵盤操作",[10,542,543],{},"Vuetify 的日期選擇器需要補上鍵盤操作和焦點管理，這部分我另外寫在《讓 Vuetify 日期選擇器支援鍵盤與螢幕報讀器》。",[26,545,547],{"id":546},"鍵盤使用者跳過區塊","鍵盤使用者：跳過區塊",[10,549,550],{},"只用鍵盤的人，每一頁都要 Tab 過整排選單才能到內容。所以每頁最上方要有一個連結，平常隱藏，Tab 聚焦時才出現：",[45,552,556],{"className":553,"code":554,"language":555,"meta":50,"style":50},"language-css shiki shiki-themes github-dark",".skip-link {\n  position: absolute;\n  top: -100px;\n}\n\n.skip-link:focus {\n  top: 0;\n}\n","css",[17,557,558,565,579,594,598,602,609,620],{"__ignoreMap":50},[54,559,560,563],{"class":56,"line":57},[54,561,562],{"class":67},".skip-link",[54,564,170],{"class":60},[54,566,567,571,573,576],{"class":56,"line":99},[54,568,570],{"class":569},"sDLfK","  position",[54,572,105],{"class":60},[54,574,575],{"class":569},"absolute",[54,577,578],{"class":60},";\n",[54,580,581,584,586,589,592],{"class":56,"line":114},[54,582,583],{"class":569},"  top",[54,585,105],{"class":60},[54,587,588],{"class":569},"-100",[54,590,591],{"class":166},"px",[54,593,578],{"class":60},[54,595,596],{"class":56,"line":134},[54,597,137],{"class":60},[54,599,600],{"class":56,"line":140},[54,601,144],{"emptyLinePlaceholder":143},[54,603,604,607],{"class":56,"line":147},[54,605,606],{"class":67},".skip-link:focus",[54,608,170],{"class":60},[54,610,611,613,615,618],{"class":56,"line":173},[54,612,583],{"class":569},[54,614,105],{"class":60},[54,616,617],{"class":569},"0",[54,619,578],{"class":60},[54,621,622],{"class":56,"line":199},[54,623,137],{"class":60},[45,625,627],{"className":87,"code":626,"language":89,"meta":50,"style":50},"function skipToMain() {\n  document.getElementById('main-content')?.focus()\n}\n",[17,628,629,640,663],{"__ignoreMap":50},[54,630,631,634,637],{"class":56,"line":57},[54,632,633],{"class":166},"function",[54,635,636],{"class":67}," skipToMain",[54,638,639],{"class":60},"() {\n",[54,641,642,645,648,651,654,657,660],{"class":56,"line":99},[54,643,644],{"class":60},"  document.",[54,646,647],{"class":67},"getElementById",[54,649,650],{"class":60},"(",[54,652,653],{"class":74},"'main-content'",[54,655,656],{"class":60},")?.",[54,658,659],{"class":67},"focus",[54,661,662],{"class":60},"()\n",[54,664,665],{"class":56,"line":114},[54,666,137],{"class":60},[10,668,669,670,673],{},"主要內容要加上 ",[17,671,672],{},"tabindex=\"-1\"","，才能接收程式設定的焦點：",[45,675,677],{"className":47,"code":676,"language":49,"meta":50,"style":50},"\u003Cmain id=\"main-content\" tabindex=\"-1\">...\u003C\u002Fmain>\n",[17,678,679],{"__ignoreMap":50},[54,680,681,683,686,689,691,694,697,699,702,705,707],{"class":56,"line":57},[54,682,61],{"class":60},[54,684,685],{"class":64},"main",[54,687,688],{"class":67}," id",[54,690,71],{"class":60},[54,692,693],{"class":74},"\"main-content\"",[54,695,696],{"class":67}," tabindex",[54,698,71],{"class":60},[54,700,701],{"class":74},"\"-1\"",[54,703,704],{"class":60},">...\u003C\u002F",[54,706,685],{"class":64},[54,708,78],{"class":60},[26,710,712],{"id":711},"導盲磚快速移動到區塊","導盲磚：快速移動到區塊",[10,714,715,716,719],{},"導盲磚是頁面上的 ",[17,717,718],{},":::"," 標記，讓使用者用快捷鍵跳到主要區塊。常見的慣例是：",[721,722,723,736],"table",{},[724,725,726],"thead",{},[727,728,729,733],"tr",{},[730,731,732],"th",{},"快捷鍵",[730,734,735],{},"區塊",[737,738,739,748,756],"tbody",{},[727,740,741,745],{},[742,743,744],"td",{},"Alt + U",[742,746,747],{},"上方區塊",[727,749,750,753],{},[742,751,752],{},"Alt + C",[742,754,755],{},"中間主要內容",[727,757,758,761],{},[742,759,760],{},"Alt + Z",[742,762,763],{},"下方區塊",[10,765,766,767,316],{},"實作上是用 ",[17,768,769],{},"accesskey",[45,771,773],{"className":47,"code":772,"language":49,"meta":50,"style":50},"\u003Ca accesskey=\"C\" href=\"#C\" title=\"中間主要內容\">:::\u003C\u002Fa>\n",[17,774,775],{"__ignoreMap":50},[54,776,777,779,781,784,786,789,791,793,796,799,801,804,807,809],{"class":56,"line":57},[54,778,61],{"class":60},[54,780,437],{"class":64},[54,782,783],{"class":67}," accesskey",[54,785,71],{"class":60},[54,787,788],{"class":74},"\"C\"",[54,790,440],{"class":67},[54,792,71],{"class":60},[54,794,795],{"class":74},"\"#C\"",[54,797,798],{"class":67}," title",[54,800,71],{"class":60},[54,802,803],{"class":74},"\"中間主要內容\"",[54,805,806],{"class":60},">:::\u003C\u002F",[54,808,437],{"class":64},[54,810,78],{"class":60},[10,812,813],{},"語法不難，專案也已經有部分頁面做了導盲磚，真正花時間的是把它補齊。檢測要求每個頁面都要有導盲磚，但原本的頁面不是依照這個需求設計的，版面結構也各不相同，沒辦法加一個共用元件就結束。",[10,815,816],{},"我只能一頁一頁調整：有的要改標題區塊的排版，有的要用定位放進既有的版面，還要留意手機版會不會擠壓到內容。同時還要讓導盲磚在每一頁的位置和樣式保持一致，使用者才不會每換一頁就要重新找。",[10,818,819],{},"一次就改了二十個檔案。如果一開始就把上、中、下區塊做進共用的頁面版型，這件事只要改一個地方。操作說明也要寫清楚快捷鍵，使用者才知道怎麼用。",[26,821,822],{"id":822},"這次學到的事",[301,824,825,828,831,834,837],{},[304,826,827],{},"無障礙是結構問題，不是最後補幾個屬性就好。越晚處理，要改的頁面越多。",[304,829,830],{},"FreeGo 這類自動工具只能抓到一部分問題，實際用鍵盤和報讀軟體走一次才準。",[304,832,833],{},"視覺和語意要分開思考，看起來是標題，不代表它就是標題。",[304,835,836],{},"共用元件先做對，後面就能少修很多頁。",[304,838,839],{},"退件不是壞事。每一次的退件原因，都讓我更清楚問題在哪裡。",[841,842,843],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":50,"searchDepth":99,"depth":99,"links":845},[846,851,852,853,858,859,860],{"id":28,"depth":99,"text":28,"children":847},[848,849,850],{"id":32,"depth":114,"text":32},{"id":81,"depth":114,"text":81},{"id":205,"depth":114,"text":205},{"id":296,"depth":99,"text":296},{"id":397,"depth":99,"text":397},{"id":462,"depth":99,"text":462,"children":854},[855,856,857],{"id":465,"depth":114,"text":465},{"id":530,"depth":114,"text":530},{"id":540,"depth":114,"text":540},{"id":546,"depth":99,"text":547},{"id":711,"depth":99,"text":712},{"id":822,"depth":99,"text":822},"quality","2026-05-13","網站送無障礙檢測時經歷多次退件，最後通過 A 等級。整理替代文字、標題階層、鍵盤操作、表單標籤、跳過區塊與導盲磚的實際修正方式。",false,"md",{},"\u002Fblog\u002Faccessibility-audit-fixes",9,{"title":5,"description":863},"blog\u002Faccessibility-audit-fixes",[872,873,874],"Accessibility","Vue 3","Vuetify","K5LisXZf0ouXT-XPvv-wJkH9vv9DhC39D_F80anpRYk"]