Keyboard Focus Order Checker: ดูหน้าเว็บของคุณในแบบที่คนใช้ keyboard เห็น
วาง HTML แล้วดูลำดับ Tab ที่คาดไว้ได้ทันที Keyboard Focus Order Checker ตรวจจับ tabindex ตั้งแต่ 1 ขึ้นไป, ปุ่มที่ใช้แค่ onclick ซึ่งคนใช้ keyboard ไปไม่ถึง และ aria-hidden focus trap — ทำงาน 100% ฝั่ง client
Table of Contents
Keyboard Focus Order Checker: ดูหน้าเว็บของคุณในแบบที่คนใช้ keyboard เห็น
การกดปุ่ม Tab คือวิธีที่คนหลายล้านคนใช้เว็บ ทั้งผู้ใช้ screen reader, ผู้ที่มีข้อจำกัดด้านการเคลื่อนไหวจนใช้เมาส์ไม่ได้ หรือ power user ที่ชอบใช้ keyboard ล้วนพึ่งพาคำสัญญาเงียบ ๆ ข้อเดียวกัน นั่นคือ focus ต้องวิ่งไปตามหน้าเว็บในลำดับที่สมเหตุสมผล และต้องหยุดตรงทุกสิ่งที่กดใช้งานได้ เมื่อคำสัญญานี้พัง หน้าเว็บจะใช้ไม่ได้ทั้งหน้าไม่ว่าจะสวยแค่ไหน — focus order ที่ผิดเป็นบั๊กไม่กี่ชนิดที่ทำให้ทั้ง checkout flow, ฟอร์มสมัคร หรือเมนูนำทางใช้งานไม่ได้เลย Keyboard Focus Order Checker ช่วยให้คุณเห็นว่าคนใช้ keyboard จะเจออะไร ก่อนที่เขาจะเจอจริง
แค่วาง (paste) HTML ลงไป เครื่องมือจะเดินเอกสารเหมือนที่ browser ทำ: สร้างรายการ expected tab order ทีละ element แล้ว audit หา pattern ที่พังบ่อยที่สุด ไม่ว่าจะเป็น element ที่ใส่ tabindex เป็นเลขบวกจนข้ามลำดับปกติ, element ที่คลิกได้อย่างเดียวเช่น div หรือ span ที่มีแต่ onclick ซึ่ง keyboard ไปถึงไม่ได้เลย และ element ที่ติด aria-hidden="true" แต่ยังมีลูกที่โฟกัสได้ข้างใน — คอมโบที่แย่ที่สุด เพราะ focus จะไปจองอยู่บนปุ่มที่มองไม่เห็น
เหมือนเครื่องมือทุกตัวในเว็บนี้ ทุกอย่างประมวลผล 100% ฝั่ง client ใน browser ของคุณ markup ไม่ถูกส่งออกไปไหน จึง audit dashboard ภายในองค์กร, template ของลูกค้า หรือ build ก่อน release ได้อย่างปลอดภัย
ทำไมต้องใช้ Keyboard Focus Order Checker?
- จำลองการกด Tab ได้โดยไม่ต้องกดจริง การไล่กด Tab ผ่านหน้าเว็บจริงช้า ต้องจำ state เอง และคลิกหลุดครั้งเดียวก็ต้องเริ่มใหม่ เครื่องมือสร้าง expected tab order ฉบับเต็มให้ในการวางครั้งเดียว เห็นทั้งเส้นทางในภาพเดียว
- จับ 3 ความพังคลาสสิกให้อัตโนมัติ positive tabindex, ปุ่มที่คลิกได้อย่างเดียว และ aria-hidden trap คือ pattern ที่ทำลาย keyboard navigation บ่อยที่สุดในโค้ดจริง ทุกแบบถูกตรวจและรายงานทันทีที่วางโค้ด
- เปลี่ยนปัญหาที่มองไม่เห็นให้เห็นตำแหน่งชัดเจน ทุก issue ระบุ line และ column พร้อม snippet ของโค้ดที่มีปัญหา กระโดดไปแก้ตรงจุดได้เลยโดยไม่ต้องไล่หาทั้งไฟล์
- วิเคราะห์เรื่อง reachability ไม่ใช่แค่ไวยากรณ์ โค้ดที่ถูกต้องตาม HTML ก็ยังอาจไปไม่ถึงด้วย keyboard เครื่องมือคิดให้ว่า element ไหนรับ focus ได้และกดใช้งานได้จริง ซึ่งเป็นสิ่งเดียวที่ผู้ใช้ keyboard สนใจ
- copy รายงานฉบับเต็มได้ในคลิกเดียว ได้รายงานพร้อมจำนวน issue และรายการทั้งหมด แปะลง pull request, ticket หรือเอกสาร accessibility audit ได้ทันที
- ไม่มีขั้นต่ำ ไม่มีข้อมูลรั่ว ไม่ต้องสมัคร ไม่ต้อง upload ไม่ต้อง install ทุกอย่างเกิดใน browser โค้ดลับของคุณจึงยังเป็นโค้ดลับ
ฟีเจอร์หลัก
| ฟีเจอร์ | ทำอะไร |
|---|---|
| Expected tab order list | ไล่ focusable element ตามลำดับที่ปุ่ม Tab จะเดินผ่านพอดี |
| ตรวจ positive tabindex | รายงานทุก tabindex ที่มากกว่า 0 ซึ่งทำลายลำดับปกติของเอกสาร |
| ตรวจ unreachable control | หา element ที่คลิกได้แต่ไม่มีทางรับ keyboard focus เลย |
| ตรวจ aria-hidden trap | จับ container ที่ซ่อนอยู่แต่ยังมีลูกที่โฟกัสได้ข้างใน |
| ระดับความรุนแรง พร้อมตำแหน่ง | error และ warning พร้อม line, column และ snippet ของโค้ด |
| Copy รายงานได้ | สรุปเป็น plain text พร้อมนับจำนวน issue ในคลิกเดียว สำหรับแปะใน ticket |
รายละเอียดที่ควรรู้อีกนิด: element ที่ไม่มีทางอยู่ใน tab order ได้เลย เช่น control ที่ถูกถอดออกจาก flow ของเอกสาร จะถูกระบุว่าถูกถอดออกจาก tab order ไม่ใช่ข้ามไปเงียบ ๆ ทำให้รายการตรงกับความจริง การวิเคราะห์อัปเดตสดขณะที่คุณแก้ markup ที่วางลงไป ทำให้การแก้เป็นลูปแน่น ๆ แก้ attribute หนึ่งบรรทัด ดูธงเตือนหายไปหน้าเครื่องมือได้เลย และมีเอกสารตัวอย่างที่ตั้งใจพังครบทุกแบบ กดดูได้ทันทีว่าทั้งสามกลุ่มปัญหาโดนตรวจจับยังไง
วิธีใช้งาน
- เปิดเครื่องมือ เข้าไปที่ Keyboard Focus Order Checker ไม่ต้อง install ไม่ต้องตั้งค่าอะไร
- วาง HTML ของคุณ จะวางทั้งหน้าหรือเฉพาะ component ที่กำลังทำอยู่ก็ได้ เช่น modal, navbar หรือฟอร์ม หรือจะโหลดตัวอย่างที่ตั้งใจพังไว้เพื่อดูการทำงานก็ได้
- อ่านรายการ expected tab order นี่คือลำดับที่ผู้ใช้ keyboard จะเจอจริง เทียบกับ layout ที่เห็นตา ตัวเลขควรไล่ตรงกับสิ่งที่ตาคาดหวังจากบนลงล่าง ซ้ายไปขวา
- แก้ issue ที่ถูกชี้ตามลำดับความสำคัญ ทุก finding แสดง severity, line, column และ snippet เริ่มจากตัวรุนแรงก่อน โดยเฉพาะ unreachable control และ hidden trap ที่มักสำคัญกว่าเรื่องลำดับเฉย ๆ
- วางใหม่แล้ว copy รายงาน หลังแก้เสร็จ วาง markup รุ่นใหม่เพื่อยืนยันว่าธงเตือนหายหมด แล้ว copy รายงานแนบไปกับ pull request หรือเอกสาร audit เป็นหลักฐาน
3 วิธีที่ focus order พังบ่อยที่สุด
บั๊ก focus order ในโลกจริงส่วนใหญ่ตกอยู่ใน 3 ถังนี้ เข้าใจว่าทำไมแต่ละแบบถึงเจ็บ และวิธีแก้เป็นอย่างไร ก็แทบจบงานแล้ว
1. Positive tabindex ทำลายลำดับธรรมชาติ
ลำดับ Tab ปกติเดินตาม DOM คือ focus วิ่งจากบนลงล่างของเอกสาร แต่การใส่ tabindex="1" หรือ tabindex="2" ดึง element ออกจาก flow นั้นไปสร้างเกาะลำดับแยกที่จัดมือ แค่มี positive สองค่าปนกับ element ปกติ ลำดับก็จะกลายเป็นอะไรที่ไม่มีใครทำนายได้ และทุกครั้งที่แก้หน้าเว็บภายหลัง ลำดับก็จะสับใหม่เงียบ ๆ อีก เป็น "แผลปิด" ยอดนิยมของ layout ที่ render ปุ่มผิดตำแหน่ง และแทบจะทำให้เลวลงเสมอ เพราะตอนนี้มีแหล่งความจริงสองที่ วิธีแก้คือลบค่า positive ทิ้งแล้วไปจัดลำดับใน DOM แทน เครื่องมือจะชี้ทุกค่า positive พร้อมตำแหน่ง เพื่อให้คุณเก็บกวาดได้ครบ ไม่ใช่ไล่เก็บทีละอันด้วยมือ
2. Element ที่คลิกได้แต่ keyboard ไปไม่ถึง
div ที่มี onclick ใช้กับเมาส์ได้เนียนสุด ๆ แต่เป็นกำแพงสำหรับ keyboard เพราะมันรับ focus ไม่ได้ Tab จึงไม่หยุดตรงนั้น และ Enter กับ Space ก็ไม่มีผลอะไร ผู้ใช้เห็นปุ่มที่ควรกดแต่ไปไม่ถึงเลย วิธีแก้ที่ถูกต้องคือใช้ element ตามความหมายจริง: ใช้ button หรือ a แล้วคุณจะได้ทั้ง focusability, การกดด้วย keyboard และ role ที่ถูกต้องมาฟรี ถ้าจำเป็นจริง ๆ ที่จะคง element ที่ไม่มีความหมายไว้ ต้องใส่ role="button" บวก tabindex="0" บวก handler keydown สำหรับ Enter และ Space เอง — แก้สามจุดเพื่อปัญหาที่ element เดียวที่ดีจะป้องกันได้ตั้งแต่แรก เครื่องมือจะ list ทุก element ที่คลิกได้แต่ keyboard ไปไม่ถึง ทำให้ไม่มีตัวไหนหลุด
3. aria-hidden ที่ยัง tab ได้อยู่
aria-hidden="true" ถอด element ออกจาก accessibility tree — screen reader จะข้ามมัน — แต่ไม่ได้ทำอะไรกับ tab order เลย container ที่ถูกซ่อนแต่ลูก ๆ ยังโฟกัสได้ จึงเกิดคอมโบที่แย่ที่สุดของ accessibility: focus ย้ายไปอยู่บนสิ่งที่มองไม่เห็น screen reader ไม่ประกาศอะไรเลย และผู้ใช้ติดค้างบน control ที่มองไม่เห็น เข้าใจไม่ได้ว่าตัวเองอยู่ตรงไหน เคสนี้เกิดบ่อยมากกับ decorative panel, dropdown ที่ปิดอยู่ และ widget แจ้งเตือนที่ลอยอยู่นอกจอ วิธีแก้คือทำให้การซ่อนเป็นจริง: ถอด focusability ของลูกด้วย tabindex="-1", ใช้ attribute hidden หรือ inert ตามควร หรือเลื่อนการ render ออกไปจนกว่าเนื้อหาจะแสดงจริง เครื่องมือตรวจจับ pattern นี้โดยเฉพาะ — container ที่ติด aria-hidden แต่ยังมีลูกที่โฟกัสได้ — และชี้ไปที่ subtree ที่มีปัญหา
ทั้งสามกรณีมีหลักการเดียวที่ชนะเสมอ: ลำดับ DOM ธรรมชาติดีกว่าการแพตช์ด้วย tabindex เสมอ จัดโครง markup ให้ source order ตรงกับ visual order เท่านั้น แล้ว tab order จะถูกต้องเองฟรี ๆ ตลอดไป ไม่ต้องเสีย attribute แม้แต่ตัวเดียว
กรณีใช้งานจริง
Audit ตาม WCAG 2.1.1 และ 2.4.3
Success Criterion 2.1.1 (Keyboard) กำหนดว่าฟังก์ชันทุกอย่างต้องใช้งานได้ผ่าน keyboard interface และ 2.4.3 (Focus Order) กำหนดว่า focus ต้องเดินในลำดับที่คงความหมายและใช้งานได้ เครื่องมือนี้ตรงกับทั้งสองข้อ: รายการ tab order เป็นหลักฐานของ 2.4.3 ส่วน finding เรื่อง unreachable control และ hidden trap คือความผิดพลาดของ 2.1.1 ที่จับต้องได้ copy รายงานแปะเข้า VPAT หรือ audit workbook ได้เลย
พัฒนา custom component
Dropdown, tabs, toolbar และ carousel คือจุดที่ทีมมักทำ focusable widget เอง — และเป็นแหล่งกำเนิดบั๊ก reachability วาง HTML ที่ render แล้วของ component เข้าเครื่องมือระหว่างพัฒนา ไม่ใช่หลังเปิดตัว การยืนยัน tab order ของ widget ใหม่ใช้เวลาไม่กี่วินาที และช่วยกันนิสัย onclick ใส่ div ก่อนที่มันจะขึ้น production
Modal dialog กับ focus trap ที่ทำถูกวิธี
Modal ที่ดีต้องคุม focus ให้อยู่ในตัวเองขณะเปิด ส่วน modal ที่พังจะปล่อยให้ focus หลุดไปหน้าหลัง หรือแย่กว่านั้นคือใส่ aria-hidden ให้พื้นหลังแต่ control บนพื้นหลังยัง tab ถึงอยู่ วางทั้งหน้าที่ modal เปิดอยู่แล้วดูว่า tab order วิ่งจริง ๆ อย่างไร: เข้า dialog, ไล่ใน dialog จนครบ แล้วกลับออกมาที่หน้าเดิมต่อเมื่อปิดเท่านั้น
แก้มโนย้อนหลัง (legacy remediation)
หน้าเว็บเก่า ๆ สะสม positive tabindex และ widget ที่คลิกได้อย่างเดียวมาเป็นสิบปี แทนที่จะไล่กด keyboard ผ่านทุก template ด้วยมือ วางทีละหน้าแล้วให้เครื่องมือสรุปลิสต์เรียงตามความสำคัญ ทีมส่วนใหญ่จะพบว่า pattern ไม่กี่แบบครอบคลุม issue ส่วนใหญ่ แก้ที่ต้นแบบครั้งเดียว ปัญหาสิบกว่าเรื่องหายพร้อมกัน
แนวปฏิบัติที่ดีที่สุด
- ทำให้ลำดับ DOM ตรงกับลำดับที่ตาเห็น ยุค CSS ปัจจุบัน (flexbox, grid) แทบไม่จำเป็นต้องให้ source order กับ visual order ต่างกันอยู่แล้ว ถ้าต่างกันจริง focus order ก็ควรตามสิ่งที่ตาเห็น แก้ที่ markup ไม่ใช่แก้ที่ focus
- ใช้ button และลิงก์จริง button หรือ a ที่มี href แก้ทั้ง focus, การกดด้วย keyboard, role และความหมายใน tag เดียว เอา role กับ tabindex มาใช้เมื่อถูกบังคับจริง ๆ เท่านั้น
- อย่าปล่อย positive tabindex ขึ้น production เจอเมื่อไรให้ถือว่าเป็น code smell แล้วลบทิ้ง DOM คือแหล่งความจริงเดียวของ tab order
- ซ่อนเมื่อไรให้ซ่อนจริง ถ้าอะไรเป็น aria-hidden ลูกที่โฟกัสได้ข้างในก็ต้องโฟกัสไม่ได้ด้วย ไม่งั้นก็ไม่ควร render ออกมาเลย
- ทดสอบด้วยการถอดเมาส์ วางเมาส์ข้าง ๆ แล้วจบ core flow ของคุณ — ค้นหา, ใส่ตะกร้า, checkout — ด้วย keyboard ล้วน เครื่องมือหาปัญหาเชิงโครงสร้าง ส่วนการกดจริงยืนยันว่าประสบการณ์เป็นอย่างไร
- ตรวจซ้ำหลัง refactor layout ทุกครั้ง การสลับลำดับ component ด้วย CSS หรือ template มองไม่เห็นใน code review แต่เห็นทันทีใน tab order วาง HTML ใหม่ก่อน merge ทุกครั้ง
พร้อมดูหน้าเว็บของคุณในแบบที่คนใช้ keyboard เห็นแล้วหรือยัง? วาง HTML ลงใน Keyboard Focus Order Checker ฟรี แล้วรับ expected tab order, positive tabindex ทุกตัว, unreachable control ทุกตัว และ aria-hidden trap ทุกจุดในไม่กี่วินาที — ทำงานใน browser ของคุณ ไม่มีอะไรถูก upload
เครื่องมืออื่น ๆ ที่น่าสนใจ:
- Accessible Name Calculator — เมื่อ focus ไปถึงแล้ว ผู้ใช้ต้องรู้ว่าสิ่งนั้นคืออะไร คำนวณได้เลยว่า screen reader จะประกาศอะไรสำหรับ element ใด ๆ
- Heading Structure Checker — focus order กับลำดับ heading คือสองครึ่งของการนำทางแบบเรียงเส้น ตรวจ heading hierarchy แบบเดียวกันได้เลย
- JSON Formatter — เวลา component ของคุณ render ข้อมูลจาก JSON payload จัดรูปแบบและไล่ดูได้ว่า value ไหนไปจบที่ control ไหน
focus order คือเส้นแบ่งระหว่างหน้าเว็บที่คนใช้ได้กับหน้าเว็บที่คนได้แค่ดู — ลองเอาหน้าของคุณเข้า Keyboard Focus Order Checker ก่อนที่ผู้ใช้ keyboard จะช่วยค้นหารอยรั่วให้คุณเอง
คำถามที่พบบ่อย
ถ: ลำดับ tab ธรรมชาติกับลำดับเมื่อมี positive tabindex ต่างกันอย่างไร?
ตอบ: ลำดับธรรมชาติเดินตาม DOM โดยเยี่ยมชม focusable element จากบนเอกสารลงล่าง ส่วนค่า positive tabindex จะสร้างลำดับแยกที่วิ่งก่อนลำดับธรรมชาติ แล้วลำดับธรรมชาติจะกลับมาต่อเมื่อค่า positive หมดแล้วเท่านั้น พอมีมากกว่าหนึ่งค่า ลำดับสุดท้ายจะยากจะคิดตามจนแทบเป็นไปไม่ได้ — นั่นคือเหตุผลที่เครื่องมือ flag ทุกตัวที่เจอ
ถ: positive tabindex เคยมีประโยชน์บ้างไหม?
ตอบ: แทบไม่มี การใช้ tabindex ที่ยอมรับกันทั่วไปมีค่าเดียวคือ -1 ซึ่งถอด element ออกจาก tab order เชิงโปรแกรม (ใช้โฟกัส modal ผ่านสคริปต์เป็นต้น) ค่า 0 ใช้บ้างใน custom widget ที่แทน element อื่นไม่ได้ ส่วนค่า 1 ขึ้นไปถือเป็น anti-pattern ถ้าลำดับผิด ให้แก้ลำดับใน DOM แทน
ถ: ทำไม aria-hidden ที่ยังมีลูกโฟกัสได้ ถึงแย่กว่าแต่ละปัญหาแยกกัน?
ตอบ: ถ้ามีแค่ aria-hidden เนื้อหาแค่ถูกซ่อนจาก screen reader ถ้ามีแค่ element ที่โฟกัสได้ มันแค่ถูกกดถึง แต่พอรวมกันมันกลายเป็นกับดัก: focus ไปอยู่บน control ที่ screen reader ไม่ประกาศและผู้ใช้มองไม่เห็น ผู้ใช้ keyboard จะรู้สึกเหมือน focus "หายไป" — กด Tab ไปเรื่อย ๆ แต่ไม่รู้ว่ามันไปจองอยู่ตรงไหน
ถ: div ของผมคลิกได้ปกติ ทำไมเครื่องมือถึง flag?
ตอบ: เพราะการคลิกด้วยเมาส์กับการกดด้วย keyboard เป็นเส้นทาง input คนละเส้นทาง div ที่มี onclick ตอบสนองเมาส์ได้ แต่รับ focus ไม่ได้ keyboard จึง trigger มันไม่ได้เลย — ไม่มี Tab stop, ไม่มี Enter, ไม่มี Space เปลี่ยนเป็น button แล้ว handler เดิมจะใช้ได้กับทั้งสองเส้นทางอัตโนมัติ
ถ: ควรวางทั้งหน้า หรือเช็คทีละ component?
ตอบ: ได้ทั้งคู่ วางทั้งหน้าเหมาะกับ audit และการทดสอบ modal เพราะเห็นเส้นทางเต็มที่ผู้ใช้ keyboard เดิน ส่วนทีละ component เหมาะตอนพัฒนา เพราะรายการสั้น อ่านเร็ว และลูปแก้ไหนเร็วกว่า หลายทีมใช้แบบวาง component ตอน build แล้ววางทั้งหน้าก่อน release