Accessible Name Calculator: ดูให้ชัดว่า screen reader จะอ่านชื่ออะไร
คำนวณ accessible name ของ element ใด ๆ จาก HTML ที่วางลงไป: เช็ค aria-labelledby เป็นสายโซ่, aria-label, label association, alt text และ inner text ตาม algorithm ของ W3C — ทำงานในเบราว์เซอร์ 100%
Table of Contents
Accessible Name Calculator: ดูให้ชัดว่า screen reader จะอ่านชื่ออะไร
Element ทุกตัวที่ interact ได้บนหน้าเว็บมีชื่ออยู่สองแบบ แบบแรกคือชื่อที่ตาเห็น — ตัวหนังสือบนปุ่มหรือในลิงก์ ส่วนแบบที่สองคือ accessible name: ข้อความที่ screen reader จะประกาศออกมา ซึ่งถูกคำนวณด้วย algorithm ที่ W3C กำหนดไว้ตายตัว จาก aria-labelledby, aria-label, alt text, label element และ inner text สองชื่อนี้เกี่ยวข้องกันแต่ไม่เหมือนกัน และช่องว่างระหว่างสองชื่อนี้คือที่ที่ปัญหา accessibility มักซ่อนอยู่
พูดตรง ๆ ก็คือ ปุ่มที่ต่อให้ screen reader ได้ยินแค่คำว่า "click here" ก็เหมือนประตูที่ล็อกอยู่ — accessible name คือกุญแจที่ไขมัน คนที่มองเห็นจะเห็นไอคอนรูปกากบาทแล้วเดาได้ว่า "ปิด" แต่ผู้ใช้ screen reader ได้ยินก็แค่คำว่า "button" ปัญหานี้แก้ด้วยการจ้องหน้าเว็บไม่ได้ ชื่อต้องถูกคำนวณด้วยวิธีเดียวกับที่ assistive technology คำนวณเท่านั้น
Accessible Name Calculator ทำงานนั้นโดยเฉพาะ วาง HTML element ใด ๆ ลงไป เครื่องมือจะไล่ algorithm ทีละขั้น — resolve สายโซ่ aria-labelledby, aria-label, label association, alt text และ inner text ที่เป็นตัวสำรอง — แล้วแสดงทั้งชื่อสุดท้ายและลำดับการ resolve ที่ทำให้เกิดชื่อนั้น ทุกอย่างทำงานในเบราว์เซอร์ 100% มาร์กอัปของคุณไม่หลุดออกจากเครื่อง
ทำไมต้องใช้ Accessible Name Calculator?
- คำนวณจริง ไม่ใช่การเดา เครื่องมือไล่ลำดับการ resolve ตาม W3C AccName specification อย่างเคร่งครัด รวมถึงกรณีเฉพาะอย่างสายโซ่ labelledby และ alt ที่ว่างเปล่า
- เห็นแหล่งที่ชนะด้วย สอง element ประกาศชื่อเดียวกันได้ด้วยเหตุผลคนละแบบ การเห็นว่าขั้นไหนของบันไดให้ชื่อ ช่วยแยกชื่อที่มาจาก inner text ที่มั่นคง ออกจากชื่อที่แขวนอยู่กับ reference ที่อาจขาด
- สายโซ่ aria-labelledby อ่านรู้เรื่องแล้ว id หลายตัวจะถูกต่อเรียงตามลำดับด้วยช่องว่าง แล้วคุณเห็นผลลัพธ์ที่ประกอบแล้ว — นี่คือวิธีเดียวที่เชื่อถือได้ในการ debug ชื่อที่ประกอบจากหลาย node
- Feedback ทันที วาง แก้ attribute แล้วอ่านใหม่ ไม่ต้อง build ไม่ต้องติดตั้ง extension วงจรการแก้วัดกันเป็นวินาที
- ใช้ได้กับทุก element ปุ่ม ลิงก์ form field รูปภาพ landmark และ ARIA widget ล้วนผ่านการคำนวณแบบเดียวกัน
- เป็นส่วนตัวโดยดีไซน์ เป็น JavaScript ล้วน ๆ ที่รันฝั่ง client แดชบอร์ดภายในและดีไซน์ที่ยังไม่เปิดตัววางได้สบาย ๆ
ฟีเจอร์หลัก
| ฟีเจอร์ | ทำอะไร |
|---|---|
| วาง HTML ได้ทั้งชิ้น | รับทั้งเอกสารเต็ม component หรือเศษ markup เปล่า ๆ |
| เอนจินตาม W3C | resolve ชื่อตามลำดับใน spec: labelledby, label, alt, inner text |
| แสดงลำดับการ resolve | เห็นทุกขั้นของบันไดและว่าแหล่งไหนชนะ |
| resolve เป็นสายโซ่ | ต่อ aria-labelledby หลาย id ตามลำดับการประกาศ |
| native semantics | รองรับ img alt, input ทุก type, label element และ heading text |
| client-side 100% | ไม่มี upload ไม่มี account ไม่มีอะไรหลุดออกจากเครื่อง |
- ใช้เป็นเอกสารได้ แปะชื่อที่คำนวณได้ลง pull request เพื่อให้คนรีวิวเห็นว่าทำไมการประกาศถึงเป็นแบบนั้น
- สอน algorithm แบบเงียบ ๆ แปะไปสักพัก คุณจะเริ่มเดาผลได้ก่อนอ่านเสียอีก
วิธีใช้งาน
- เปิด Accessible Name Calculator
- วาง element ที่อยากเช็ค — ปุ่มเดี่ยว ๆ, ลิงก์ครอบโลโก้, field พร้อม label ของมัน หรือเศษ markup ใหญ่ ๆ ที่มีมันอยู่ข้างใน
- อ่านชื่อที่คำนวณได้ แล้วอ่านบันไดต่อว่าแหล่งไหนชนะ
- ปรับ markup — เพิ่ม aria-label ที่หาย แก้ id ที่ห้อยลอย คืน inner text — แล้วดูชื่ออัปเดต
- ไล่ทำกับ interactive element ที่เหลือ และเอา fix ไปใส่ในซอร์สจริง ไม่ใช่แค่ในชิ้นที่แปะไว้
บันไดการ resolve ชื่อ (Name Resolution Ladder)
Algorithm ของ W3C เหมือนบันไดที่คุณไต่ลงไปเรื่อย ๆ จนเจอขั้นที่ให้ข้อความ ลำดับตายตัว: aria-labelledby ก่อน ต่อด้วย aria-label, alt text สำหรับรูปภาพ, label element ที่ผูกอยู่ และปิดท้ายด้วย inner text ตาม native semantics ขั้นแรกที่ให้ข้อความคือผู้ชนะ — และทุกอย่างที่อยู่ถัดลงไปจะถูกมองข้ามทั้งหมด ประโยคหลังนี่แหละที่นักพัฒนามักลืม
ปุ่มไอคอนล้วนคือความล้มเหลวคลาสสิกของ WCAG ลองดูปุ่มปิดที่ render แค่กากบาท:
<button class="icon-btn"> <svg aria-hidden="true">...</svg> </button>
ไม่มี labelledby ไม่มี label ไม่มี alt ไม่มี inner text — การคำนวณไต่หลุดขอบล่างของบันได ชื่อจึงว่างเปล่า screen reader ประกาศได้มากที่สุดแค่ "button" ซึ่งไม่ผ่าน WCAG 4.1.2 Name, Role, Value วิธีแก้ใช้ attribute เดียว: <button class="icon-btn" aria-label="Close dialog"> การเปลี่ยนปุ่มตัวหนังสือเป็นไอคอนคือวิธีคลาสสิกที่ component ที่เคย accessible พังแบบเงียบ ๆ ปุ่มไอคอนจึงควรโดนเช็คชื่อทุกครั้งที่ redesign
สายโซ่ aria-labelledby ต่อหลาย id ด้วยช่องว่าง algorithm จะเก็บข้อความของแต่ละ element ที่ถูกอ้างถึง เรียงตามลำดับ id ที่เขียนไว้ แล้วต่อกัน นี่คือวิธีประกอบชื่อจากหลาย node กระจัดกระจาย — ดูตัวอย่างลิงก์โลโก้:
<a href="/" aria-labelledby="brand tagline"> <img id="brand" src="logo.png" alt="Acme Analytics" /> <span id="tagline">Stop guessing. Start measuring.</span> </a>
สายโซ่อ่าน brand ก่อน tagline ทีหลัง ชื่อที่ได้คือ "Acme Analytics Stop guessing. Start measuring." สลับลำดับ id เมื่อไร ลำดับการประกาศก็กลับตาม กฎมีสองข้อ: id ที่ถูกอ้างถึงต้องมีอยู่จริงทุกตัว และลำดับ id คือลำดับการประกาศ
ข้อผิดพลาดที่เจอบ่อย aria-label ที่ซ้ำกับข้อความที่มองเห็นไม่เพิ่มอะไรและชวนให้เพี้ยน เพราะสักวันหนึ่งฝ่ายใดฝ่ายหนึ่งจะถูกแก้โดยอีกฝ่ายไม่รู้ ปุ่มพี่น้องหลายตัวที่ประกาศว่า "Close" เหมือนกันหมดทำให้ผู้ใช้ต้องเดาว่าปิด dialog ไหน และ aria-label บน span หรือ div ธรรมดาจะถูกมองข้ามทั้งหมด — generic role ไม่รองรับการตั้งชื่อ
กรณีใช้งานจริง
ตรวจปุ่มไอคอน
วางปุ่มไอคอนล้วนใน toolbar ทีละตัว — share, settings, delete, close — แล้วยืนยันว่ามีชื่อเฉพาะเจาะจงไม่ซ้ำใคร ตามปกติคุณมักเจอสักตัวที่ประกาศแค่ "button" และอีกตัวที่ประกาศชื่อของเพื่อนบ้าน
เช็ค form field
field สามารถดูมี label ทั้งที่ประกาศไม่ได้ วาง input พร้อม markup รอบ ๆ แล้วตรวจว่า <label for> เชื่อมกันจริง หรือ reference labelledby ชี้ไปที่มีอยู่จริง
ทำเอกสาร component library
จดชื่อที่คำนวณได้ของ markup มาตรฐานของแต่ละ component ไว้ ผู้ร่วมพัฒนาจะได้สัญญาที่ชัดเจน — "ปุ่ม delete ต้องประกาศว่า Delete task" — และ regression กลายเป็น diff ที่รีวิวได้
ทำตาม WCAG 4.1.2
เกณฑ์ 4.1.2 Name, Role, Value กำหนดให้ชื่อต้องระบุได้จากโค้ด ขั้นตอนคือเช็คชื่อของทุก control แล้วอะไรที่ว่างหรือไม่สื่อความถือเป็น finding ที่แก้แล้วตรวจซ้ำได้ทันที
Best Practices
- ให้ข้อความที่มองเห็นเป็นชื่อ ถ้าปุ่มเขียนว่า "Save" ชื่อก็ควรมาจากข้อความนั้น — แหล่งเดียวย่อมไม่เพี้ยน
- ใช้ native element ก่อนเสมอ <button> จริงตั้งชื่อให้ตัวเองฟรี ส่วน div ที่ role="button" ทำให้คุณต้องดูแลชื่อ role และการใช้ keyboard เอง
- ให้ aria-label เพิ่มข้อมูล บอกให้ได้ว่าไอคอนแปลวอะไรหรือ control ตัวนี้คุมรายการไหน ไม่ใช่ทวนข้อความที่มองเห็นอยู่แล้ว
- เขียนสายโซ่ labelledby อย่างตั้งใจ เรียง id ตามลำดับที่ชื่อควรถูกอ่าน รักษาทุก reference ให้มีชีวิต และเช็คซ้ำหลังเปลี่ยนชื่อ id
- เช็คชื่อซ้ำหลัง redesign การสลับไอคอน การตัดข้อความ และการแปลภาษา ล้วนเปลี่ยนหรือลบชื่อ ให้คำนวณใหม่ก่อนขึ้นของจริง
- ทดสอบกับ screen reader จริงด้วย เครื่องมือนี้เป๊ะและไว แต่ VoiceOver และ NVDA ยืนยันประสบการณ์เต็มรูปแบบได้ — ใช้ทั้งคู่
ลอง element แรกของคุณตอนนี้เลย
เปิด Accessible Name Calculator เอาปุ่มไอคอนล่าสุดที่คุณเพิ่ง ship มาวางลงไป แล้วอ่านดูว่า screen reader จะพูดจริง ๆ ว่าอะไร ถ้าผลลัพธ์ทำให้คุณตกใจ แปลว่าเครื่องมือนี้คุ้มค่าตัวแล้ว
เครื่องมือที่เกี่ยวข้อง:
- Heading Structure Checker — ตรวจโครง H1–H6 ให้ screen reader นำทางได้ถูกต้อง
- Keyboard Focus Order Checker — ยืนยันว่าลำดับ tab ตรงกับลำดับการอ่านด้วยตา
- JSON Formatter — จัดรูปแบบและตรวจ JSON จากไฟล์ config และ API response
ขอให้ทุกปุ่มที่คุณ ship พูดชื่อของมันเองได้นะ
คำถามที่พบบ่อย
ถ: accessible name คืออะไรกันแน่?
ตอบ: คือข้อความที่ assistive technology ประกาศให้ element หนึ่ง ๆ คำนวณด้วย W3C AccName algorithm จากสายโซ่ aria-labelledby, aria-label, alt text, label element และ inner text ตามลำดับความสำคัญที่ตายตัว
ถ: ปุ่มผมมีทั้ง aria-label และข้อความที่มองเห็น ตัวไหนชนะ?
ตอบ: aria-label ชนะ เพราะอยู่สูงกว่า inner text บนบันได นี่เองที่ทำให้ aria-label ที่ไม่ตรงกับข้อความที่เห็นเป็นเรื่องเสี่ยง: ผู้ใช้ได้ยินชื่อ control ที่ต่างจากที่ตาเห็น
ถ: aria-labelledby ข้ามเนื้อหาข้างใน element ไปเลยหรือ?
ตอบ: ใช่ เมื่อ reference resolve ได้ ข้อความที่ต่อกันจะกลายเป็นชื่อทั้งหมด และ inner text ถูกมองข้าม ส่วน reference ที่ชี้ไม่เจออะไรเลยจะถูกข้าม แล้วการคำนวณไต่บันไดต่อไป
ถ: เครื่องมืออัปโหลด HTML ของผมไปไหนหรือเปล่า?
ตอบ: ไม่ การคำนวณรันในเบราว์เซอร์ของคุณล้วน ๆ ด้วย JavaScript ฝั่ง client ดีไซน์ที่ยังไม่เปิดตัวและ markup ภายในจึงไม่หลุดออกจากเครื่อง