สร้างระบบเมนูสำหรับผู้พิการ
เผยแพร่แล้ว: 2022-03-10หมายเหตุบรรณาธิการ : บทความนี้เดิมปรากฏบนส่วนประกอบที่รวม หากคุณต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับบทความเกี่ยวกับส่วนประกอบที่ครอบคลุมที่คล้ายกัน ให้ติดตาม @inclusicomps บน Twitter หรือสมัครรับฟีด RSS ด้วยการสนับสนุน inclusive-components.design บน Patreon คุณสามารถช่วยให้ฐานข้อมูลนี้เป็นฐานข้อมูลที่ครอบคลุมที่สุดของส่วนประกอบอินเทอร์เฟซที่มีประสิทธิภาพ
การจำแนกประเภทเป็นเรื่องยาก ยกตัวอย่างปู ปูเสฉวน ปูกระเบื้อง และปูเกือกม้า ไม่ใช่ปู ที่แท้จริง แต่นั่นไม่ได้หยุดเราใช้คำต่อท้าย "ปู" มันจะยิ่งสับสนมากขึ้นเรื่อยๆ เมื่อเวลาผ่านไป และด้วยกระบวนการที่เรียกว่าการ ก่อมะเร็ง ปูที่ไม่จริงจะมีวิวัฒนาการให้คล้ายกับปูจริงมากขึ้น นี่เป็นกรณีของปูราชาซึ่งเชื่อกันว่าเป็นปูเสฉวนในสมัยก่อน ลองนึกภาพขนาดของเปลือกหอย!
ในการออกแบบ เรามักจะทำผิดพลาดเหมือนกันในการตั้งชื่อให้สิ่งของต่างๆ กัน พวกเขา ดูเหมือน คล้ายกัน แต่รูปลักษณ์ภายนอกสามารถหลอกลวงได้ ซึ่งอาจส่งผลเสียต่อความชัดเจนของไลบรารีคอมโพเนนต์ของคุณ ในแง่ของการรวม ยังอาจนำคุณไปสู่การนำองค์ประกอบที่ไม่เหมาะสมทางความหมายและพฤติกรรมมาใช้ใหม่ ผู้ใช้จะคาดหวังสิ่งหนึ่งและรับอีกสิ่งหนึ่ง
คำว่า "ดรอปดาวน์" เป็นตัวอย่างคลาสสิก "ดรอปดาวน์" มากมายในอินเทอร์เฟซ รวมถึงชุดของ <option>
จากองค์ประกอบ <select>
และรายการลิงก์ที่ JavaScript เปิดเผยซึ่งประกอบขึ้นเป็นเมนูย่อยการนำทาง ชื่อเดียวกัน; สิ่งที่แตกต่างกันมาก (บางคนเรียกว่า "การดึงลง" แน่นอน แต่อย่าพูดถึงเรื่องนี้)
ดรอปดาวน์ที่ประกอบเป็นชุดตัวเลือกมักเรียกว่า "เมนู" และฉันต้องการพูดถึงสิ่งเหล่านี้ที่นี่ เรากำลังคิดเมนูเด็ดอยู่ แต่ระหว่างทางก็มีเมนูที่ไม่ จริง มากมายให้พูดถึง
มาเริ่มกันที่ควิซ กล่องของลิงก์ห้อยลงมาจากแถบนำทางในภาพประกอบเป็นเมนูหรือไม่
คำตอบคือ ไม่ใช่ ไม่ใช่เมนูจริง
เป็นข้อตกลงที่มีมาช้านานว่า schema การนำทางประกอบด้วยรายการลิงก์ อนุสัญญาที่ดำเนินมายาวนานเกือบเท่ากับว่าควรมีการจัดเตรียมการนำทางย่อยเป็นรายการลิงก์ที่ ซ้อนกัน หากฉันต้องลบ CSS สำหรับองค์ประกอบที่แสดงด้านบน ฉันควรเห็นสิ่งต่อไปนี้ ยกเว้นสีน้ำเงินและใน Times New Roman
พูดตามความหมายแล้ว รายการลิงก์ที่ซ้อนกันนั้นถูกต้องในบริบทนี้ ระบบนำทางคือ สารบัญ จริงๆ และนี่คือวิธีการจัดโครงสร้างสารบัญ สิ่งเดียวที่ทำให้เรานึกถึง "เมนู" จริงๆ ก็คือการจัดสไตล์ของรายการที่ซ้อนกันและวิธีการเปิดเผยเมื่อวางเมาส์เหนือหรือโฟกัส
นั่นคือสิ่งที่ผิดพลาดและเริ่มเพิ่มความหมายของ WAI-ARIA: aria-haspopup="true"
, role="menu"
, role="menuitem"
เป็นต้น มีที่สำหรับสิ่งเหล่านี้ที่เราจะกล่าวถึง แต่ไม่ใช่ที่นี่ . นี่คือสาเหตุสองประการ:
- เมนู ARIA ไม่ได้กำหนดไว้สำหรับการนำทาง แต่สำหรับลักษณะการทำงานของแอปพลิเคชัน ลองนึกภาพระบบเมนูสำหรับแอปพลิเคชันเดสก์ท็อป
- ลิงก์ระดับบนสุดควรใช้ เป็นลิงก์ หมายความว่าลิงก์ไม่ทำงานเหมือนปุ่มเมนู
เกี่ยวกับ (2): เมื่อสำรวจพื้นที่การนำทางด้วยเมนูย่อย คาดว่าแต่ละเมนูย่อยจะปรากฏขึ้นเมื่อวางเมาส์เหนือหรือเน้นที่ลิงก์ "ระดับบนสุด" ("ร้านค้า" ในภาพประกอบ) ข้อมูลนี้จะแสดงเมนูย่อยและวางลิงก์ของตนเองในลำดับโฟกัส ด้วยความช่วยเหลือเล็กน้อยจาก JavaScript ในการจับโฟกัสและเบลอเหตุการณ์ เพื่อรักษาลักษณะที่ปรากฏของเมนูย่อยในขณะที่จำเป็น ผู้ที่ใช้แป้นพิมพ์ควรจะสามารถแท็บผ่านแต่ละลิงก์ของแต่ละระดับได้
ปุ่มเมนูที่ใช้คุณสมบัติ aria-haspopup="true"
ไม่ทำงานเช่นนี้ พวกเขาจะเปิดใช้งานเมื่อ คลิก และไม่มีจุดประสงค์อื่นนอกจากการเปิดเผยเมนูลับ
ตามภาพ ไม่ว่าเมนูนั้นจะเปิดหรือปิดอยู่ ควรสื่อสารกับ aria-expanded
คุณควรเปลี่ยนสถานะนี้เมื่อคลิกเท่านั้น ไม่ใช่ที่โฟกัส ผู้ใช้มักไม่คาดหวังการเปลี่ยนแปลงอย่างชัดแจ้งในเหตุการณ์โฟกัส ในระบบนำทางของเรา สถานะไม่เปลี่ยนแปลงจริงๆ มันเป็นเพียงเคล็ดลับการจัดสไตล์ ตามพฤติกรรม เราสามารถ แท็บ ผ่านการนำทางราวกับว่าไม่มีการแสดง/ซ่อนกลอุบายดังกล่าวเกิดขึ้น
ปัญหาเกี่ยวกับเมนูย่อยการนำทาง
เมนูย่อยการนำทาง (หรือ "ดรอปดาวน์" สำหรับบางเมนู) ใช้งานได้ดีกับเมาส์หรือคีย์บอร์ด แต่ก็ไม่ได้ร้อนแรงนักเมื่อต้องสัมผัส เมื่อคุณกดลิงก์ "ร้านค้า" ระดับบนสุดในตัวอย่างของเราเป็นครั้งแรก แสดงว่าคุณกำลังบอกให้เปิดเมนูย่อยทั้งสองและไปตามลิงก์
มีสองวิธีแก้ปัญหาที่เป็นไปได้ที่นี่:
- ป้องกันพฤติกรรมเริ่มต้นของลิงก์ระดับบนสุด (
e.preventDefault()
) และสคริปต์ในความหมายและการทำงานของเมนู WAI-ARIA แบบเต็ม - ตรวจสอบให้แน่ใจว่าหน้าปลายทางระดับบนสุดแต่ละหน้ามีสารบัญแทนเมนูย่อย
(1) ไม่เป็นที่น่าพอใจเพราะอย่างที่ฉันได้กล่าวไว้ก่อนหน้านี้ ความหมายและพฤติกรรมประเภทนี้ไม่คาดหวังในบริบทนี้ โดยที่ลิงก์เป็นตัวควบคุมหัวเรื่อง ผู้ใช้พลัสไม่สามารถนำทางไปยังหน้าระดับบนสุดได้อีกต่อไป หากมี
Sidenote: อุปกรณ์ใดบ้างที่เป็นอุปกรณ์ระบบสัมผัส
การคิดว่า "นี่ไม่ใช่วิธีแก้ปัญหาที่ดี แต่ฉันจะเพิ่มสำหรับอินเทอร์เฟซแบบสัมผัสเท่านั้น" เป็นเรื่องที่น่าดึงดูดใจ ปัญหาคือ จะตรวจจับได้อย่างไรว่าอุปกรณ์มีหน้าจอสัมผัสหรือไม่
คุณไม่ควรเทียบ "หน้าจอเล็ก" กับ "เปิดใช้งานระบบสัมผัส" อย่างแน่นอน เคยทำงานในสำนักงานเดียวกันกับคนทำระบบสัมผัสสำหรับพิพิธภัณฑ์ ฉันรับรองได้เลยว่าหน้าจอที่ใหญ่ที่สุดบางหน้าจอเป็นหน้าจอสัมผัส แล็ปท็อปคีย์บอร์ดคู่และอินพุตแบบสัมผัสก็มีจำนวนมากขึ้นเช่นกัน
ในทำนองเดียวกัน อุปกรณ์ขนาดเล็กจำนวนมากแต่ไม่ใช่ทั้งหมดเป็นอุปกรณ์ระบบสัมผัส ในการออกแบบที่ครอบคลุม คุณไม่สามารถที่จะตั้งสมมติฐานได้
ความละเอียด (2) มีความครอบคลุมและแข็งแกร่งมากขึ้น โดยมี "ทางเลือก" สำหรับผู้ใช้อินพุตทั้งหมด แต่คำพูดที่น่ากลัวเกี่ยวกับคำศัพท์ทางเลือกที่นี่ค่อนข้างรอบคอบเพราะฉันคิดว่าสารบัญในหน้าเป็นวิธีการที่ ดีกว่า ในการนำทาง
ทีมบริการดิจิทัลของรัฐบาลที่ได้รับรางวัลดูเหมือนจะเห็นด้วย คุณอาจเคยเห็นพวกเขาในวิกิพีเดีย
สารบัญ
สารบัญคือการนำทางสำหรับหน้าที่เกี่ยวข้องหรือส่วนของหน้า และควรมีความหมายคล้ายกับพื้นที่การนำทางของไซต์หลัก โดยใช้องค์ประกอบ <nav>
รายการ และกลไกการติดป้ายกำกับกลุ่ม
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->
หมายเหตุ
- ในตัวอย่างนี้ เรากำลังจินตนาการว่าแต่ละส่วนเป็นหน้าของตัวเอง อย่างที่ควรจะเป็นในเมนูย่อยแบบเลื่อนลง
- สิ่งสำคัญคือหน้า "ร้านค้า" แต่ละหน้ามีโครงสร้างเหมือนกัน โดยมีสารบัญ "ผลิตภัณฑ์" อยู่ในที่เดียวกัน ความสม่ำเสมอสนับสนุนความเข้าใจ
- รายการจัดกลุ่มรายการและแจกแจงรายการในเอาต์พุตเทคโนโลยีอำนวยความสะดวก เช่น เสียงสังเคราะห์ของโปรแกรมอ่านหน้าจอ
-
<nav>
มีป้ายกำกับซ้ำโดยส่วนหัวโดยใช้aria-labelledby
ซึ่งหมายความว่า "การนำทางผลิตภัณฑ์" จะประกาศในโปรแกรมอ่านหน้าจอส่วนใหญ่เมื่อเข้าสู่ภูมิภาคด้วย แท็บ นอกจากนี้ยังหมายความว่า "การนำทางผลิตภัณฑ์" จะถูกจัดรายการในอินเทอร์เฟซองค์ประกอบโปรแกรมอ่านหน้าจอ ซึ่งผู้ใช้สามารถนำทางไปยังภูมิภาคได้โดยตรง
ทั้งหมดในหน้าเดียว
หากคุณสามารถจัดส่วนทั้งหมดลงในหน้าเดียวได้โดยไม่ใช้เวลานานและยากต่อการเลื่อน ก็ยิ่งดีไปอีก เพียงเชื่อมโยงไปยังตัวระบุแฮชของแต่ละส่วน ตัวอย่างเช่น href="#waffle-irons"
ควรชี้ไปที่ .
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->
( หมายเหตุ: เบราว์เซอร์บางตัวไม่สามารถส่งโฟกัสไปยังแฟรกเมนต์ของหน้าที่เชื่อมโยงได้ การวาง tabindex="-1"
บนแฟรกเมนต์เป้าหมายช่วยแก้ไขปัญหานี้ได้)
ในกรณีที่ไซต์มีเนื้อหาจำนวนมาก สถาปัตยกรรมข้อมูลที่สร้างขึ้นอย่างระมัดระวัง ซึ่งแสดงออกผ่านการใช้ "เมนู" ของสารบัญแบบเสรีนิยมมากกว่าระบบดรอปดาวน์ที่ไม่ปลอดภัยและเทอะทะ ไม่เพียงแต่จะทำให้การตอบสนองง่ายขึ้นเท่านั้น และต้องใช้โค้ดน้อยกว่าในการทำเช่นนั้น แต่ยังทำให้สิ่งต่างๆ ชัดเจนขึ้น โดยที่ระบบดรอปดาวน์จะซ่อนโครงสร้างไว้ สารบัญจะเปิดเผย
บางไซต์ รวมถึง gov.uk ของ Government Digital Service มีหน้าดัชนี (หรือ "หัวข้อ") ที่เป็น เพียง สารบัญ เป็นแนวคิดที่ทรงพลังที่ Hugo ตัวสร้างไซต์สแตติกยอดนิยมสร้างหน้าดังกล่าวโดยค่าเริ่มต้น
สถาปัตยกรรมสารสนเทศเป็นส่วนสำคัญของการรวม ไซต์ที่มีการจัดระเบียบไม่ดีสามารถปฏิบัติตามข้อกำหนดทางเทคนิคได้ตามที่คุณต้องการ แต่จะทำให้ผู้ใช้จำนวนมากแปลกแยกออกไป โดยเฉพาะอย่างยิ่งผู้ที่มีความบกพร่องทางสติปัญญาหรือผู้ที่ถูกกดดันให้ใช้เวลา
ปุ่มเมนูนำทาง
ในขณะที่เรากำลังพูดถึงเมนูเกี่ยวกับการนำทางมารยาท ฉันจะไม่พูดถึงปุ่มเมนูการนำทาง คุณคงเคยเห็นสิ่งเหล่านี้เขียนแทนด้วยไอคอน "แฮมเบอร์เกอร์" หรือ "นาวิคอน" สามบรรทัด
แม้จะมีสถาปัตยกรรมข้อมูลแบบแยกย่อยและลิงก์การนำทางเพียงชั้นเดียว พื้นที่บนหน้าจอขนาดเล็กก็ยังมีราคาสูง การซ่อนการนำทางหลังปุ่มหมายความว่ามีพื้นที่มากขึ้นสำหรับเนื้อหาหลักในวิวพอร์ต
ปุ่มนำทางเป็นสิ่งที่ใกล้เคียงที่สุดที่เราเคยศึกษามาจนถึงปุ่มเมนู จริง เนื่องจากมีวัตถุประสงค์เพื่อสลับความพร้อมใช้งานของเมนูเมื่อคลิก จึงควร:
- ระบุตัวเองว่าเป็นปุ่ม ไม่ใช่ลิงก์
- ระบุสถานะขยายหรือยุบของเมนูที่เกี่ยวข้อง (ซึ่งในแง่ที่เข้มงวด เป็นเพียงรายการลิงก์)
การเพิ่มประสิทธิภาพแบบก้าวหน้า
แต่ขอไม่ไปข้างหน้าของตัวเอง เราควรคำนึงถึงการปรับปรุงแบบก้าวหน้าและพิจารณาว่าจะทำงานอย่างไรหากไม่มี JavaScript
ในเอกสาร HTML ที่ไม่ได้ปรับปรุง คุณทำอะไรกับปุ่มได้ไม่มาก (ยกเว้นปุ่มส่ง แต่นั่นไม่ได้เกี่ยวข้องอย่างใกล้ชิดกับสิ่งที่เราต้องการทำให้สำเร็จที่นี่) บางทีเราควรเริ่มต้นด้วยลิงก์ที่นำเราไปยังการนำทางแทน
<a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
ไม่มีประโยชน์มากมายในการมีลิงก์ เว้นแต่จะมีเนื้อหามากมายระหว่างลิงก์และการนำทาง เนื่องจากการนำทางไซต์ควรปรากฏใกล้กับด้านบนสุดของลำดับต้นทางเกือบทุกครั้ง จึงไม่มีความจำเป็น ดังนั้น จริงๆ แล้ว เมนูการนำทางในกรณีที่ไม่มี JavaScript ควรจะเป็น... การนำทางบางส่วน
<nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
คุณปรับปรุงสิ่งนี้โดยเพิ่มปุ่ม ในสถานะเริ่มต้น และซ่อนการนำทาง (โดยใช้แอตทริบิวต์ที่ hidden
):
<nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
เบราว์เซอร์รุ่นเก่าบางตัว — คุณรู้ว่าเบราว์เซอร์ใด — ไม่รองรับ hidden
ดังนั้นอย่าลืมใส่สิ่งต่อไปนี้ใน CSS ของคุณ แก้ไขปัญหาได้เนื่องจาก display: none
มีผลเช่นเดียวกันกับการซ่อนเมนูจากเทคโนโลยีช่วยเหลือและลบลิงก์ออกจากลำดับโฟกัส
[hidden] { display: none; }
แน่นอนว่าการพยายามอย่างเต็มที่เพื่อสนับสนุนซอฟต์แวร์รุ่นเก่าคือการออกแบบที่ครอบคลุม บางคนไม่สามารถหรือไม่เต็มใจที่จะอัพเกรด
ตำแหน่ง
จุดที่หลายคนผิดพลาดคือการวางปุ่มไว้ นอก ภูมิภาค ซึ่งหมายความว่าผู้ใช้โปรแกรมอ่านหน้าจอที่ย้ายไปที่ <nav>
โดยใช้ทางลัดจะพบว่าว่างเปล่า ซึ่งไม่เป็นประโยชน์มากนัก ด้วยรายการที่ซ่อนอยู่จากโปรแกรมอ่านหน้าจอ พวกเขาจะพบสิ่งนี้:
<nav> </nav>
ต่อไปนี้คือวิธีที่เราอาจสลับสถานะ:
var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });
aria-controls
ตามที่ฉันเขียนใน Aria-controls Is Poop แอตทริบิวต์ aria-controls
ซึ่งมีจุดมุ่งหมายเพื่อช่วยให้ผู้ใช้โปรแกรมอ่านหน้าจอนำทางจากองค์ประกอบการควบคุมไปยังองค์ประกอบที่ควบคุม ได้รับการสนับสนุนในโปรแกรมอ่านหน้าจอ JAWS เท่านั้น ดังนั้นคุณจึงไม่สามารถพึ่งพาได้
หากไม่มีวิธีการที่ดีในการชี้นำผู้ใช้ระหว่างองค์ประกอบ คุณควรตรวจสอบให้แน่ใจว่าข้อใดข้อหนึ่งต่อไปนี้เป็นจริง:
- ลิงก์แรกของรายการที่ขยายจะอยู่ถัดไปในลำดับโฟกัสหลังปุ่ม (ดังในตัวอย่างโค้ดก่อนหน้า)
- ลิงก์แรกเน้นโดยทางโปรแกรมเมื่อเปิดเผยรายการ
ในกรณีนี้ ผมขอแนะนำ (1) มันง่ายกว่ามากเพราะคุณไม่ต้องกังวลเกี่ยวกับการย้ายโฟกัสกลับไปที่ปุ่มและเหตุการณ์ที่ต้องทำ นอกจากนี้ ขณะนี้ยังไม่มีสิ่งใดที่จะเตือนผู้ใช้ว่าโฟกัสของพวกเขาจะถูกย้ายไปยังที่อื่น ในเมนูจริงเราจะพูดถึงในไม่ช้า นี่คืองานของ aria-haspopup="true"
การใช้ aria-controls
ไม่ได้ทำอันตรายอะไรมาก ยกเว้นแต่จะทำให้การอ่านข้อมูลในโปรแกรมอ่านหน้าจอมีความละเอียดมากขึ้น อย่างไรก็ตาม ผู้ใช้ JAWS บางคนอาจคาดหวังไว้ นี่คือวิธีนำไปใช้โดยใช้ id
ของรายการเป็นรหัส:
<nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
บทบาทของเมนูและรายการเมนู
เมนู จริง (ในความหมายของ WAI-ARIA) ควรระบุตัวเองโดยใช้บทบาท menu
(สำหรับคอนเทนเนอร์) และโดยทั่วไปแล้ว menuitem
ของรายการเมนู (อาจใช้บทบาทย่อยอื่นๆ) บทบาทผู้ปกครองและเด็กเหล่านี้ทำงานร่วมกันเพื่อให้ข้อมูลแก่เทคโนโลยีอำนวยความสะดวก ต่อไปนี้คือวิธีการเพิ่มรายการเพื่อให้มีความหมายของเมนู:
<ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>
เนื่องจากเมนูการนำทางของเราเริ่มมีลักษณะเหมือนเมนู "จริง" จึงไม่ควรปรากฏ
คำตอบสั้น ๆ คือ: ไม่ คำตอบที่ยาวเหยียดคือ: ไม่ เนื่องจากรายการของเรามีลิงก์และองค์ประกอบ menuitem
ไม่ได้มีไว้สำหรับลูกหลานแบบโต้ตอบ นั่น คือ การควบคุมในเมนู
แน่นอน เราสามารถระงับความหมายของรายการของ <li>
โดยใช้ role="presentation"
หรือ role="none"
(ซึ่งเทียบเท่ากัน) และวางบทบาท menuitem
ในแต่ละลิงก์ อย่างไรก็ตาม สิ่งนี้จะระงับบทบาทลิงก์โดยนัย กล่าวอีกนัยหนึ่ง ตัวอย่างต่อไปนี้จะประกาศเป็น "หน้าแรก รายการเมนู" ไม่ใช่ "หน้าแรก ลิงก์" หรือ "หน้าแรก รายการเมนู ลิงก์" บทบาท ARIA จะแทนที่บทบาท HTML เพียงอย่างเดียว
<!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>
เราต้องการให้ผู้ใช้รู้ว่าพวกเขากำลังใช้ลิงก์และสามารถคาดหวังพฤติกรรมของลิงก์ได้ ดังนั้นนี่จึงไม่ใช่เรื่องดี อย่างที่ฉันพูด เมนูจริงมีไว้สำหรับลักษณะการทำงานของแอปพลิเคชัน (ที่ขับเคลื่อนด้วย JavaScript)
สิ่งที่เราเหลือคือองค์ประกอบไฮบริดชนิดหนึ่ง ซึ่งไม่ใช่เมนูจริง แต่อย่างน้อยก็บอกผู้ใช้ว่ารายการลิงก์เปิดอยู่หรือไม่ ขอบคุณสถานะ aria-expanded
นี่เป็นรูปแบบที่น่าพอใจอย่างยิ่งสำหรับเมนูการนำทาง
Sidenote: <select>
องค์ประกอบ
หากคุณเคยมีส่วนร่วมในการออกแบบที่ตอบสนองได้ตั้งแต่ต้น คุณอาจจำรูปแบบที่การนำทางถูกย่อให้เป็นองค์ประกอบ <select>
สำหรับวิวพอร์ตแบบแคบ
เช่นเดียวกับปุ่มสลับตามช่องทำเครื่องหมายที่เราได้พูดคุยกัน การใช้องค์ประกอบดั้งเดิมที่ทำงานค่อนข้างตามที่ตั้งใจไว้โดยไม่ต้องเขียนสคริปต์เพิ่มเติมเป็นทางเลือกที่ดีสำหรับประสิทธิภาพและ — โดยเฉพาะบนอุปกรณ์เคลื่อนที่ — ประสิทธิภาพ และองค์ประกอบ <select>
เป็น เมนูที่มีลักษณะแปลก ๆ โดยมีความหมายคล้ายกับเมนูที่เรียกปุ่มซึ่งเราจะสร้างขึ้นในไม่ช้า
อย่างไรก็ตาม เช่นเดียวกับปุ่มสลับช่องทำเครื่องหมาย เรากำลังใช้องค์ประกอบที่เกี่ยวข้องกับการป้อนข้อมูล ไม่ใช่แค่การเลือก ซึ่งอาจก่อให้เกิดความสับสนสำหรับผู้ใช้จำนวนมาก โดยเฉพาะอย่างยิ่งเนื่องจากรูปแบบนี้ใช้ JavaScript เพื่อทำให้ <option>
ที่เลือกทำงานเหมือนลิงก์ การเปลี่ยนแปลงบริบทที่ไม่คาดคิดซึ่งทำให้เกิดนี้ถือเป็นความล้มเหลวตามเกณฑ์ 3.2.2 On Input (ระดับ A) ของ WCAG
เมนูทรู
ตอนนี้เราได้หารือเกี่ยวกับเมนูเท็จและกึ่งเมนูแล้ว ถึงเวลาสร้างเมนู ที่แท้จริง แล้ว โดยเปิดและปิดด้วยปุ่มเมนูจริง จากนี้ไปในฉันจะอ้างถึงปุ่มและเมนูร่วมกันเป็นเพียงแค่ "ปุ่มเมนู"
แต่ปุ่มเมนูของเราจะเป็นจริงในด้านใดบ้าง? มันจะเป็นองค์ประกอบเมนูที่มีไว้สำหรับเลือกตัวเลือกในแอปพลิเคชันหัวเรื่อง ซึ่งใช้ความหมายที่คาดหวังและพฤติกรรมที่เกี่ยวข้องทั้งหมดเพื่อพิจารณาแบบธรรมดาสำหรับเครื่องมือดังกล่าว
ดังที่ได้กล่าวไปแล้ว ข้อตกลงเหล่านี้มาจากการออกแบบแอปพลิเคชันเดสก์ท็อป การระบุแหล่งที่มาของ ARIA และการจัดการโฟกัสที่ควบคุมด้วย JavaScript เป็นสิ่งจำเป็นเพื่อเลียนแบบทั้งหมด จุดประสงค์ส่วนหนึ่งของ ARIA คือการช่วยให้นักพัฒนาเว็บสร้างประสบการณ์เว็บที่สมบูรณ์โดยไม่ทำลายข้อตกลงการใช้งานที่ปลอมแปลงขึ้นในโลกดั้งเดิม
ในตัวอย่างนี้ เราจะจินตนาการว่าแอปพลิเคชันของเราเป็นเกมหรือแบบทดสอบ ปุ่มเมนูของเราจะให้ผู้ใช้เลือกระดับความยาก ด้วยความหมายทั้งหมด เมนูจะมีลักษณะดังนี้:
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>
หมายเหตุ
- คุณสมบัติ
aria-haspopup
บ่งชี้ว่าปุ่มนี้สร้างเมนู มันทำหน้าที่เตือนว่าเมื่อกดแล้วผู้ใช้จะถูกย้ายไปที่เมนู "ป๊อปอัป" (เราจะกล่าวถึงพฤติกรรมการโฟกัสในไม่ช้า) ค่าของมันไม่เปลี่ยนแปลง — ยังคงเป็นtrue
ตลอดเวลา -
<span>
ภายในปุ่มมีจุดยูนิโค้ดสำหรับสามเหลี่ยมเล็กชี้ลงสีดำ แบบแผนนี้บ่งชี้ด้วยสายตาว่าaria-haspopup
ทำอะไรโดยไม่มองเห็นได้ — การกดปุ่มจะแสดงบางสิ่งที่อยู่ด้านล่าง การระบุแหล่งที่มาaria-hidden="true"
ป้องกันไม่ให้โปรแกรมอ่านหน้าจอประกาศ "สามเหลี่ยมชี้ลง" หรือที่คล้ายกัน ขอบคุณaria-haspopup
มันไม่จำเป็นในบริบทที่ไม่ใช่ภาพ - คุณสมบัติ
aria-haspopup
เสริมด้วยaria-expanded
สิ่งนี้จะบอกผู้ใช้ว่าขณะนี้เมนูอยู่ในสถานะเปิด (ขยาย) หรือปิด (ยุบ) โดยการสลับระหว่างค่าtrue
และfalse
- เมนูนี้ใช้บทบาท
menu
(ชื่อที่เหมาะเจาะ) มันใช้ทายาทที่มีบทบาทmenuitem
พวกเขาไม่จำเป็นต้องเป็นลูกโดยตรงขององค์ประกอบmenu
แต่ในกรณีนี้ - เพื่อความเรียบง่าย
แป้นพิมพ์และพฤติกรรมการโฟกัส
เมื่อพูดถึงการทำให้แป้นพิมพ์ควบคุมแบบโต้ตอบสามารถเข้าถึงได้ สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือการใช้องค์ประกอบที่เหมาะสม เนื่องจากเราใช้องค์ประกอบ <button>
ที่นี่ เราจึงมั่นใจได้ว่าเหตุการณ์การคลิกจะเริ่มขึ้นเมื่อกดแป้น Enter และ Space ตามที่ระบุไว้ในอินเทอร์เฟซ HTMLButtonElement นอกจากนี้ยังหมายความว่าเราสามารถปิดการใช้งานรายการเมนูโดยใช้คุณสมบัติ disabled
การใช้งานที่เกี่ยวข้องกับปุ่ม
การโต้ตอบกับแป้นพิมพ์ของปุ่มเมนูยังมีอะไรอีกมากมาย ต่อไปนี้คือข้อมูลสรุปของการโฟกัสและการทำงานของแป้นพิมพ์ทั้งหมดที่เราจะนำไปใช้ โดยยึดตามแนวทางปฏิบัติการเขียน WAI-ARIA 1.1:
ป้อน , เว้น วรรค หรือ ↓ บนปุ่มเมนู | เปิดเมนู |
↓ ในรายการเมนู | ย้ายโฟกัสไปที่รายการเมนูถัดไป หรือรายการเมนูแรกหากคุณอยู่ที่รายการสุดท้าย |
↑ ในรายการเมนู | ย้ายโฟกัสไปที่รายการเมนูก่อนหน้า หรือรายการเมนูสุดท้ายหากคุณอยู่ที่รายการแรก |
↑ บนปุ่มเมนู | ปิดเมนูหากเปิดอยู่ |
Esc ในรายการเมนู | ปิดเมนูและโฟกัสที่ปุ่มเมนู |
ข้อดีของการย้ายโฟกัสระหว่างรายการเมนูโดยใช้ปุ่มลูกศรคือ Tab จะถูกสงวนไว้สำหรับการย้ายออกจากเมนู ในทางปฏิบัติ หมายความว่าผู้ใช้ไม่ต้องเลื่อนดูทุกรายการเมนูเพื่อออกจากเมนู — การปรับปรุงอย่างมากสำหรับการใช้งาน โดยเฉพาะอย่างยิ่งเมื่อมีรายการเมนูมากมาย
การใช้งาน tabindex="-1"
ทำให้รายการเมนูไม่สามารถโฟกัสได้ด้วย Tab แต่ยังคงความสามารถในการโฟกัสองค์ประกอบโดยทางโปรแกรม เมื่อจับภาพการกดปุ่มบนปุ่มลูกศร
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>
วิธีการเปิด
เป็นส่วนหนึ่งของการออกแบบเสียง API เราสามารถสร้างวิธีการในการจัดการเหตุการณ์ต่างๆ
ตัวอย่างเช่น วิธี open
จำเป็นต้องเปลี่ยนค่า aria-expanded
เป็น "จริง" เปลี่ยนคุณสมบัติที่ซ่อนอยู่ของเมนูเป็น false
และเน้น menuitem
แรกในเมนูที่ไม่ถูกปิดใช้งาน:
MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }
เราสามารถใช้วิธีนี้โดยที่ผู้ใช้กดปุ่มลงบนอินสแตนซ์ปุ่มเมนูที่เน้น:
this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));
นอกจากนี้ นักพัฒนาที่ใช้สคริปต์นี้จะสามารถเปิดเมนูโดยทางโปรแกรมได้แล้ว:
exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();
Sidenote: ช่องทำเครื่องหมาย hack
มากที่สุดเท่าที่เป็นไปได้ เป็นการดีกว่าที่จะไม่ใช้ JavaScript เว้นแต่ว่าคุณต้องการ การนำเทคโนโลยีที่สามมาใช้กับ HTML และ CSS จำเป็นต้องเพิ่มความซับซ้อนและความเปราะบางของระบบ อย่างไรก็ตาม ไม่ใช่ทุกองค์ประกอบที่สามารถสร้างขึ้นได้อย่างน่าพอใจโดยไม่ต้องใช้ JavaScript ในการผสมผสาน
ในกรณีของปุ่มเมนู ความกระตือรือร้นในการทำให้ปุ่มเหล่านี้ "ทำงานได้โดยไม่ต้องใช้ JavaScript" ทำให้เกิดสิ่งที่เรียกว่าการแฮ็กช่องทำเครื่องหมาย นี่คือตำแหน่งที่ใช้สถานะที่เลือก (หรือไม่ได้เลือก) ของช่องทำเครื่องหมายที่ซ่อนอยู่เพื่อสลับการมองเห็นองค์ประกอบเมนูโดยใช้ CSS
/* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }
สำหรับผู้ใช้โปรแกรมอ่านหน้าจอ บทบาทช่องทำเครื่องหมายและสถานะที่เลือกนั้นไร้สาระในบริบทนี้ สิ่งนี้สามารถเอาชนะได้บางส่วนโดยการเพิ่ม role="button"
ลงในช่องทำเครื่องหมาย
<input type="checkbox" role="button" aria-haspopup="true">
น่าเสียดายที่สิ่งนี้ยับยั้งการสื่อสารสถานะที่ตรวจสอบโดยนัย ทำให้เราขาดการตอบกลับสถานะที่ปราศจาก JavaScript (แย่แม้ว่าจะถูก "ตรวจสอบ" ในบริบทนี้)
แต่ สามารถ ปลอมแปลง aria-expanded
ได้ เราเพียงแค่ต้องจัดหาฉลากของเราด้วยช่วงสองช่วงด้านล่าง
<input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded</span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">▾</span> </label>
สิ่งเหล่านี้ถูกซ่อนด้วยสายตาโดยใช้คลาส visually-hidden
แต่ — ขึ้นอยู่กับสถานะที่เราอยู่ใน — มีเพียงคนเดียวเท่านั้นที่ถูกซ่อนสำหรับโปรแกรมอ่านหน้าจอเช่นกัน นั่นคือ มีเพียงรายการเดียวเท่านั้นที่มี display: none
และสิ่งนี้ถูกกำหนดโดยสถานะที่ตรวจสอบที่ยังหลงเหลืออยู่ (แต่ไม่ได้สื่อสาร):
/* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }
นี่เป็นสิ่งที่ฉลาดและทั้งหมด แต่ปุ่มเมนูของเรายังไม่สมบูรณ์เนื่องจากพฤติกรรมการโฟกัสที่คาดหวังที่เราได้พูดคุยกันนั้นไม่สามารถนำไปใช้ได้หากไม่มี JavaScript
ลักษณะการทำงานเหล่านี้เป็นแบบทั่วไปและคาดว่าจะทำให้ปุ่มใช้งานได้มากขึ้น อย่างไรก็ตาม หากคุณต้องการใช้ปุ่มเมนูโดยไม่มี JavaScript จริงๆ สิ่งนี้จะใกล้เคียงที่สุดเท่าที่จะทำได้ เมื่อพิจารณาถึงปุ่มเมนูการนำทางแบบย่อที่ฉันกล่าวถึงก่อนหน้านี้มีเนื้อหาเมนูที่ ไม่ ขึ้นกับ JavaScript (เช่น ลิงก์) วิธีนี้อาจเป็นตัวเลือกที่เหมาะสม
เพื่อความสนุก นี่คือ codePen ที่ใช้ปุ่มเมนูการนำทางแบบไม่มี JavaScript
ดูตัวอย่างปุ่มเมนูการนำทางด้วยปากกา ไม่มี JS โดย Heydon (@heydon) บน CodePen
( หมายเหตุ: เฉพาะ Space เท่านั้นที่เปิดเมนูได้)
งาน “เลือก”
การดำเนินการบางวิธีควรปล่อยเหตุการณ์เพื่อให้เราสามารถตั้งค่าผู้ฟังได้ ตัวอย่างเช่น เราสามารถปล่อยเหตุการณ์ select เมื่อผู้ใช้คลิก choose
รายการเมนู เราสามารถตั้งค่านี้ได้โดยใช้ CustomEvent
ซึ่งช่วยให้เราส่งอาร์กิวเมนต์ไปยังคุณสมบัติ detail
ของเหตุการณ์ได้ ในกรณีนี้ อาร์กิวเมนต์ (“ตัวเลือก”) จะเป็นโหนด DOM ของรายการเมนูที่เลือก
MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }
มีหลายสิ่งที่เราสามารถทำได้ด้วยกลไกนี้ บางทีเราอาจตั้งค่าภูมิภาคแบบสดด้วย id
ของ menuFeedback
:
<div role="alert"></div>
ตอนนี้ เราสามารถตั้งค่าผู้ฟังและเติมข้อมูลในพื้นที่สดด้วยข้อมูลลับภายในเหตุการณ์:
exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
เมื่อเลือกรายการเมนูแล้ว ผู้ใช้โปรแกรมอ่านหน้าจอจะได้ยินว่า “คุณเลือก [ป้ายกำกับรายการเมนู]” ภูมิภาคที่ใช้งานจริง (กำหนดที่นี่ด้วยการแสดงที่มาของ role=“alert”
) ประกาศเนื้อหาในโปรแกรมอ่านหน้าจอทุกครั้งที่เนื้อหาเปลี่ยนแปลง ภูมิภาคที่ใช้งานจริงไม่ได้บังคับ แต่เป็นตัวอย่างของสิ่งที่อาจเกิดขึ้นในอินเทอร์เฟซเพื่อตอบสนองต่อผู้ใช้ที่ทำการเลือกเมนู
ทางเลือกที่คงอยู่
รายการเมนูบางรายการไม่ได้มีไว้สำหรับการเลือกการตั้งค่าแบบถาวร หลายคนทำเหมือนปุ่มมาตรฐานที่ทำให้บางอย่างในอินเทอร์เฟซเกิดขึ้นเมื่อกด อย่างไรก็ตาม ในกรณีของปุ่มเมนูความยาก เราต้องการระบุว่าการตั้งค่าใดคือการตั้งค่าความยากในปัจจุบัน — อันที่เลือกไว้สุดท้าย
แอ็ตทริบิวต์ aria-checked="true"
ใช้ได้กับรายการที่แทนที่ menuitem
มีบทบาท menuitemradio
มาร์กอัปที่ปรับปรุงแล้วโดยเลือกรายการที่สอง ( set ) มีลักษณะดังนี้:
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>
เมนูเนทีฟบนหลายแพลตฟอร์มระบุรายการที่เลือกโดยใช้เครื่องหมายถูก เราสามารถทำได้โดยไม่มีปัญหาโดยใช้ CSS เพิ่มเติมเล็กน้อย:
[role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }
ขณะสำรวจเมนูโดยที่โปรแกรมอ่านหน้าจอทำงาน การเน้นรายการที่เลือกนี้จะแจ้งประกาศ เช่น “เครื่องหมายถูก รายการเมนูขนาดกลาง ตรวจสอบแล้ว”
ลักษณะการทำงานในการเปิดเมนูด้วยเมนูที่เลือก menuitemradio
แตกต่างกันเล็กน้อย แทนที่จะโฟกัสรายการแรก (เปิดใช้งาน) ในเมนู รายการที่ เลือก จะถูกโฟกัสแทน
ประโยชน์ของพฤติกรรมนี้คืออะไร? ผู้ใช้ (ผู้ใช้ทุกคน) จะได้รับการเตือนถึงตัวเลือกที่เลือกไว้ก่อนหน้านี้ ในเมนูที่มีตัวเลือกที่เพิ่มขึ้นมากมาย (เช่น ชุดระดับการซูม) ผู้ที่ใช้แป้นพิมพ์จะอยู่ในตำแหน่งที่เหมาะสมที่สุดเพื่อทำการปรับ
การใช้ปุ่มเมนูกับโปรแกรมอ่านหน้าจอ
ในวิดีโอนี้ ฉันจะแสดงให้คุณเห็นว่าการใช้ปุ่มเมนูกับโปรแกรมอ่านหน้าจอ Voiceover และ Chrome เป็นอย่างไร ตัวอย่างใช้รายการที่มี menuitemradio
aria-checked
และพฤติกรรมการโฟกัสที่กล่าวถึง ประสบการณ์ที่คล้ายคลึงกันสามารถคาดหวังได้ในทุกช่วงของซอฟต์แวร์โปรแกรมอ่านหน้าจอยอดนิยม
ปุ่มเมนูรวมบน Github
ฉันกับคิตตี้ จิราเดลได้ทำงานร่วมกันเพื่อสร้างส่วนประกอบปุ่มเมนูด้วยคุณสมบัติ API ที่ฉันอธิบาย และอื่นๆ อีกมากมาย คุณมี Hugo ที่ต้องขอบคุณสำหรับฟีเจอร์เหล่านี้มากมาย เนื่องจากพวกมันทำงานบน a11y-dialog ซึ่งเป็นกล่องโต้ตอบโมดอลที่เข้าถึงได้ มีอยู่ใน Github และ NPM
npm i inclusive-menu-button --save
นอกจากนี้ Kitty ได้สร้างเวอร์ชัน React สำหรับการเลือกของคุณ
รายการตรวจสอบ
- อย่าใช้ความหมายของเมนู ARIA ในระบบเมนูนำทาง
- บนไซต์ที่มีเนื้อหาหนัก อย่าซ่อนโครงสร้างในเมนูการนำทางแบบดรอปดาวน์ที่ซ้อนอยู่
- ใช้
aria-expanded
เพื่อระบุสถานะเปิด/ปิดของเมนูการนำทางที่เปิดใช้งานปุ่ม - ตรวจสอบให้แน่ใจว่าเมนูการนำทางดังกล่าวอยู่ในลำดับโฟกัสถัดจากปุ่มที่เปิด/ปิด
- อย่าเสียสละความสามารถในการใช้งานในการแสวงหาโซลูชันที่ปราศจาก JavaScript มันเป็นโต๊ะเครื่องแป้ง