التنقل بلوحة المفاتيح
السماح لجميع المستخدمين بالتنقل بكفاءة دون استخدام الماوس
اختصارات لوحة المفاتيح
- Tab
- التنقل بين العناصر التفاعلية
- Shift + Tab
- التنقل للخلف
- Entrée / Espace
- تنشيط الأزرار والروابط
- Échap
- إغلاق النوافذ المنبثقة والقوائم
- ↑ ↓
- التنقل في القوائم والقوائم
التنقل بلوحة المفاتيح في القوائم
❌ مثال سيء
قائمة مع التنقل بـ Tab فقط:
<!-- سيء: التنقل بـ Tab فقط -->
<div class="menu">
<div tabindex="0">🏠 الرئيسية</div>
<div tabindex="0">📋 المشاريع</div>
<div tabindex="0">👥 الفريق</div>
<div tabindex="0">📊 الإحصائيات</div>
<div tabindex="0">⚙️ الإعدادات</div>
</div>
<!-- يجب على المستخدم الضغط على Tab 5 مرات -->
<!-- لا يوجد تنقل بالأسهم -->
<!-- لا توجد دلالات قائمة -->✅ مثال جيد
قائمة مع التنقل بالأسهم:
💡 استخدم ↑ ↓ للتنقل، Enter للاختيار
<!-- جيد: التنقل بالأسهم مع roving tabindex -->
<div
role="menu"
@keydown="handleKeydown"
>
<div
v-for="(item, i) in items"
role="menuitem"
:tabindex="selected === i ? 0 : -1"
@focus="selected = i"
:class="{ selected: selected === i }"
>
{{ item }}
</div>
</div>// التنقل بالأسهم مع roving tabindex
function handleKeydown(e) {
switch(e.key) {
case 'ArrowDown':
selected = (selected + 1) % items.length
break
case 'ArrowUp':
selected = selected === 0
? items.length - 1
: selected - 1
break
case 'Enter':
selectItem(selected)
break
}
}الشرح: تسمح القوائم القابلة للتنقل بلوحة المفاتيح باستخدام مفاتيح الأسهم للتنقل بين العناصر، بالإضافة إلى التنقل الكلاسيكي بـ Tab.
أنماط التركيز المرئية
❌ مثال سيء
جرب التنقل بـ Tab في هذه المنطقة:
/* ❌ يزيل جميع مؤشرات التركيز */
*:focus {
outline: none !important;
}
button:focus,
a:focus,
input:focus {
outline: none !important;
}<!-- ❌ مشاكل إمكانية الوصول: -->
<button>زر 1</button> <!-- لا يوجد نوع، تركيز غير مرئي -->
<button>زر 2</button> <!-- لا يوجد نوع، تركيز غير مرئي -->
<a href="#">رابط</a> <!-- تركيز غير مرئي -->
<input type="text" /> <!-- تركيز غير مرئي -->
<!-- التنقل بلوحة المفاتيح مستحيل المتابعة بصريًا -->✅ مثال جيد
جرب التنقل بـ Tab في هذه المنطقة:
/* ✅ مؤشرات تركيز مرئية ويمكن الوصول إليها */
button:focus-visible {
outline: 3px solid #4F46E5;
outline-offset: 2px;
}
input:focus-visible {
border-color: #4F46E5;
outline: 2px solid #4F46E5;
outline-offset: 1px;
}<button type="button">زر 1</button>
<button type="button">زر 2</button>
<a href="#">رابط إلى صفحة</a>
<input type="text" placeholder="أدخل نصًا" />الشرح: يحتاج المستخدمون الذين يتنقلون بلوحة المفاتيح إلى مؤشرات بصرية واضحة لمعرفة مكانهم على الصفحة.
رابط التخطي (الانتقال إلى المحتوى)
❌ مثال سيء
المحتوى الرئيسي
يجب على المستخدم التنقل عبر جميع روابط التنقل...
<!-- سيء: لا يوجد رابط تخطي -->
<header>
<nav>
<a href="#">الرئيسية</a>
<a href="#">المنتجات</a>
<a href="#">الخدمات</a>
<!-- 10+ روابط تنقل... -->
</nav>
</header>
<main>
<!-- يجب على المستخدم المرور عبر جميع عناصر التنقل -->
<h1>المحتوى الرئيسي</h1>
</main>✅ مثال جيد
المحتوى الرئيسي
يمكن للمستخدم الوصول مباشرة إلى هنا بفضل رابط التخطي!
/* رابط التخطي مخفي افتراضيًا، مرئي عند التركيز */
.skip-link {
position: absolute;
top: -100px;
left: 1rem;
z-index: 1000;
padding: 0.5rem 1rem;
background: var(--color-primary);
color: white;
text-decoration: none;
border-radius: 0.625rem;
}
.skip-link:focus {
top: 1rem;
}<a href="#main-content" class="skip-link">
الانتقال إلى المحتوى الرئيسي
</a>
<header>
<nav aria-label="التنقل الرئيسي">
<ul>
<li><a href="/">الرئيسية</a></li>
<li><a href="/produits">المنتجات</a></li>
<li><a href="/services">الخدمات</a></li>
</ul>
</nav>
</header>
<main id="main-content">
<h1>المحتوى الرئيسي</h1>
</main>الشرح: تسمح روابط التخطي لمستخدمي لوحة المفاتيح بالانتقال مباشرة إلى المحتوى الرئيسي دون الحاجة إلى المرور عبر جميع عناصر التنقل.
نافذة منبثقة مع فخ التركيز
❌ مثال سيء
<!-- سيء: نافذة منبثقة بدون إدارة التركيز -->
<div v-if="modalOpen" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>تأكيد الإجراء</h3>
<button @click="close">×</button>
</div>
<div class="modal-body">
<p>هل أنت متأكد من أنك تريد المتابعة؟</p>
<input type="text" placeholder="السبب (اختياري)" />
</div>
<div class="modal-footer">
<button @click="close">إلغاء</button>
<button @click="confirm">تأكيد</button>
</div>
</div>
</div>✅ مثال جيد
<!-- جيد: نافذة منبثقة مع فخ التركيز -->
<div
v-if="modalOpen"
class="modal-overlay"
@click="closeModal"
@keydown.esc="closeModal"
>
<div
class="modal-content"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
@click.stop
>
<header class="modal-header">
<h2 id="modal-title">تأكيد الإجراء</h2>
<button
type="button"
@click="closeModal"
aria-label="إغلاق النافذة المنبثقة"
class="close-btn"
>×</button>
</header>
<div class="modal-body">
<p>هل أنت متأكد من أنك تريد المتابعة؟</p>
<input
ref="firstInput"
type="text"
placeholder="السبب (اختياري)"
@keydown.tab="handleFocusTrap"
/>
</div>
<footer class="modal-footer">
<button
type="button"
@click="closeModal"
@keydown.tab="handleFocusTrap"
>إلغاء</button>
<button
type="button"
ref="lastButton"
@click="confirmAction"
@keydown.tab="handleFocusTrap"
>تأكيد</button>
</footer>
</div>
</div>// وظائف أساسية :
// - تركيز تلقائي على العنصر الأول
// - فخ التركيز (Tab/Shift+Tab)
// - إغلاق عند Escape
// - إرجاع التركيز إلى عنصر التشغيل
function handleFocusTrap(event) {
const focusableElements = modalContent.querySelectorAll(
'button, input, [tabindex]:not([tabindex="-1"])'
)
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault()
lastElement.focus()
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault()
firstElement.focus()
}
}الشرح: يجب أن تحافظ النوافذ المنبثقة على التركيز بالداخل وتسمح بالإغلاق بمفتاح Escape. يجب أن يعود التركيز إلى العنصر الذي فتح النافذة المنبثقة.