الوصولية (Accessibility / a11y) تضمن أن موقعك يعمل للجميع — بمن فيهم ذوو الإعاقات.
لماذا مهمة؟ • 15% من سكان العالم لديهم إعاقة ما • قانونية: بعض الدول توجب الوصولية قانونياً (ADA في أمريكا) • SEO: محركات البحث تفضل المحتوى الواضح والمنظّم
مبادئ WCAG 4: 1. قابل للإدراك (Perceivable) 2. قابل للتشغيل (Operable) 3. قابل للفهم (Understandable) 4. متين (Robust)
أساسيات ARIA (Accessible Rich Internet Applications): • role: تحديد دور العنصر (button, navigation, alert...) • aria-label: تسمية غير مرئية • aria-labelledby: ربط بعنصر مرئي للتسمية • aria-describedby: وصف تفصيلي • aria-hidden: إخفاء من قارئات الشاشة • aria-expanded: حالة القوائم المنسدلة • aria-live: منطقة تتحدث لقارئ الشاشة عند التحديث',
| 1 | <!-- 1. alt وصفي لكل الصور --> |
| 2 | <img src="logo.png" alt="شعار نوفيل — منصة تعليم البرمجة"> |
| 3 | <!-- للصور الزخرفية: alt فارغ --> |
| 4 | <img src="decoration.png" alt=""> |
| 5 | |
| 6 | <!-- 2. labels لكل حقول الإدخال --> |
| 7 | <label for="search-input">ابحث في الموقع</label> |
| 8 | <input type="search" id="search-input" name="q"> |
| 9 | |
| 10 | <!-- 3. focus management --> |
| 11 | <button type="button" aria-expanded="false" id="menu-btn" |
| 12 | onclick="toggleMenu()"> |
| 13 | القائمة |
| 14 | </button> |
| 15 | |
| 16 | <!-- 4. landmarks ARIA --> |
| 17 | <header role="banner">...</header> |
| 18 | <nav role="navigation" aria-label="التنقل الرئيسي">...</nav> |
| 19 | <main role="main">...</main> |
| 20 | <footer role="contentinfo">...</footer> |
| 21 | |
| 22 | <!-- 5. Skip navigation --> |
| 23 | <a href="#main-content" class="skip-link">تخطي للمحتوى الرئيسي</a> |
| 24 | |
| 25 | <!-- 6. aria-live لتحديثات ديناميكية --> |
| 26 | <div aria-live="polite" aria-atomic="true" id="notification"> |
| 27 | <!-- الإشعارات تُقرأ تلقائياً --> |
| 28 | </div> |
| 29 | |
| 30 | <!-- 7. أيقونات بدون نص --> |
| 31 | <button aria-label="إغلاق النافذة"> |
| 32 | <svg aria-hidden="true">...</svg> |
| 33 | </button> |
مثال 1: زر قائمة منسدلة وصولي
| 1 | <!-- زر قائمة وصولي كامل --> |
| 2 | <div class="dropdown"> |
| 3 | <button |
| 4 | type="button" |
| 5 | id="user-menu-btn" |
| 6 | aria-haspopup="true" |
| 7 | aria-expanded="false" |
| 8 | aria-controls="user-menu" |
| 9 | > |
| 10 | <img src="/avatar.jpg" alt="" aria-hidden="true"> |
| 11 | <span>أحمد محمد</span> |
| 12 | <svg aria-hidden="true"><!-- أيقونة سهم --></svg> |
| 13 | </button> |
| 14 | |
| 15 | <ul |
| 16 | id="user-menu" |
| 17 | role="menu" |
| 18 | aria-labelledby="user-menu-btn" |
| 19 | hidden |
| 20 | > |
| 21 | <li role="none"> |
| 22 | <a href="/profile" role="menuitem">الملف الشخصي</a> |
| 23 | </li> |
| 24 | <li role="none"> |
| 25 | <a href="/settings" role="menuitem">الإعدادات</a> |
| 26 | </li> |
| 27 | <li role="none"> |
| 28 | <button type="button" role="menuitem">تسجيل الخروج</button> |
| 29 | </li> |
| 30 | </ul> |
| 31 | </div> |
| 32 | |
| 33 | <script> |
| 34 | const btn = document.getElementById('user-menu-btn'); |
| 35 | const menu = document.getElementById('user-menu'); |
| 36 | |
| 37 | btn.addEventListener('click', () => { |
| 38 | const isOpen = btn.getAttribute('aria-expanded') === 'true'; |
| 39 | btn.setAttribute('aria-expanded', !isOpen); |
| 40 | menu.hidden = isOpen; |
| 41 | }); |
| 42 | |
| 43 | // إغلاق بـ ESC |
| 44 | document.addEventListener('keydown', (e) => { |
| 45 | if (e.key === 'Escape') { |
| 46 | btn.setAttribute('aria-expanded', 'false'); |
| 47 | menu.hidden = true; |
| 48 | btn.focus(); |
| 49 | } |
| 50 | }); |
| 51 | </script> |