الوحدة 3 · المكوّنات
الحالات وإمكانية الوصول، مبنيّة من الأساس
22 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
ابنِ كل حالات التفاعل وسلوكيات إمكانية الوصول داخل المكوّنات افتراضيًا، بما فيها التركيز المرئي عبر :focus-visible، وحالتي التعطيل والضغط، وأنماط ARIA التي تتبع WAI-ARIA Authoring Practices.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
بدا Button الذي أطلقته Northwind عام 2021 مثاليًا في كل لقطة شاشة، لأن اللقطات لا تُظهر إلا الحالة الافتراضية. في الإنتاج لم يكن له تركيز مرئي للوحة المفاتيح، وفشل نمط التعطيل الخاص به في التباين على علامة Tidewater، وكانت حالة «التحميل» تجعل الزر ينكمش والتخطيط يقفز. لم يكن شيء من هذا في ملف Figma. المكوّنات تفشل في حالاتها، لذا يجب أن يصمّمها النظام كلها ويبنيها ويختبرها.
مصفوفة الحالات
كل مكوّن تفاعلي يحصل على مصفوفة حالات: صف لكل variant، وعمود لكل حالة. للـ Button:
| الحالة | ما يطلقها | تُبنى بـ |
|---|---|---|
| Hover | المؤشر فوقه | :hover |
| التركيز المرئي | تركيز لوحة المفاتيح | :focus-visible |
| Active | أثناء الضغط | :active |
| معطّل | غير متاح | disabled أو aria-disabled="true" |
| تحميل | إجراء قيد التنفيذ | aria-busy، وعرض ثابت |
| مضغوط | زر التبديل مفعّل | aria-pressed="true" |
تصمّم Northwind كل خانة في Figma (كـ variants للمكوّن) قبل أن يكتب أحد سطر كود، والاختبارات البصرية في القسم 4 تعرض كل خانة في كل theme. وحين تصل مساهمة لم يُصمَّم فيها إلا الحالة الافتراضية والـ hover، تعيدها المراجعة.
تركيز يراه الناس
مستخدمو لوحة المفاتيح يحتاجون أن يروا أين التركيز؛ وWCAG 2.2 يشترط ذلك في المستوى AA (2.4.7، Focus Visible). أما مستخدمو الفأرة فعادةً لا يريدون حلقة بعد النقر. و:focus-visible يحلّ ذلك: تطبّقه المتصفّحات حين يجب أن يكون التركيز مرئيًا، وعادةً بعد التنقّل بلوحة المفاتيح، لا بعد معظم نقرات الفأرة.
:root {
--nw-focus-ring-color: var(--nw-blue-600);
--nw-focus-ring-width: 2px;
--nw-focus-ring-offset: 2px;
}
.nw-button:focus-visible {
outline: var(--nw-focus-ring-width) solid var(--nw-focus-ring-color);
outline-offset: var(--nw-focus-ring-offset);
}الحلقة مبنية من tokens، فيستطيع الوضع الداكن وTidewater تعديلها، وكل المكوّنات تستخدم الحلقة نفسها. وهي outline لا box-shadow، لأن أوضاع forced-colors مثل themes التباين في Windows تحذف الظلال وتُبقي الـ outlines.
التعطيل، بطريقتين
السمة disabled تُخرج الزر من ترتيب التنقّل بـ Tab ومن التفعيل. وهذا صحيح في معظم الحالات، لكنه يخلق مشكلة: لا يستطيع مستخدمو لوحة المفاتيح وقارئ الشاشة الوصول إلى الزر ليعرفوا لماذا هو معطّل.
حين تحتاج إلى شرح، استخدم aria-disabled="true" بدلًا من ذلك. يبقى الزر قابلًا للتركيز ويُعلَن أنه غير متاح؛ وتربط السبب عبر aria-describedby؛ ولأن المتصفّح لن يمنع النقر عنك، يجب أن يفعل المكوّن ذلك:
button.addEventListener('click', (event) => {
if (button.getAttribute('aria-disabled') === 'true') {
event.preventDefault();
event.stopImmediatePropagation();
}
});الـ Button في النظام يفعل هذا داخليًا، فتحدّد الفرق prop واحدًا وتحصل على السلوك. وهذا هو جوهر بناء إمكانية الوصول داخل المكوّن: أربعون فريقًا لن يتذكّر كلٌّ منهم stopImmediatePropagation، لكن مكوّنًا واحدًا يستطيع.
أزرار التبديل، والأهداف، والأنماط
الزر الذي يشغّل شيئًا ويطفئه هو زر تبديل (toggle button). علّم حالته بـ aria-pressed="true" أو "false"، ونسّق هذه السمة نفسها حتى لا تتباعد الحالة المرئية عن الحالة المُعلَنة:
.nw-button[aria-pressed="true"] {
--nw-button-bg: var(--nw-color-action-bg-subtle);
--nw-button-border: var(--nw-color-action-bg);
}والحجم مهمّ أيضًا: معيار النجاح 2.5.8 في WCAG 2.2 يطلب أهدافًا للمؤشر لا تقل عن 24 في 24 بكسل CSS في المستوى AA، مع بعض الاستثناءات. وأصغر ارتفاع لعنصر تحكّم في Northwind هو 32px، تفرضه tokens الحجم sm.
ولأي شيء أبعد من الزر، استخدم العناصر الأصلية أولًا: <button> و<dialog> و<details> و<select>. وحين تبني عنصرًا مخصّصًا مثل التبويبات أو زر القائمة أو الـ combobox، اتبع النمط المقابل في WAI-ARIA Authoring Practices Guide: أدواره وحالاته وتفاعلات لوحة المفاتيح فيه. مستخدمو التقنيات المساعدة يتوقّعون هذه السلوكيات، والدليل يوثّقها بدقّة.
في التمرين تصلح مجموعة أزرار تخفي تركيز لوحة المفاتيح وتتجاهل حالة التعطيل الخاصة بها. وبعدها تكتب التوثيق الذي يخبر الفرق كيف تستخدم كل هذا.
أهم النقاط
- صمّم وابنِ مصفوفة حالات كاملة لكل مكوّن تفاعلي، تشمل التركيز والتعطيل والتحميل والضغط، لا الحالة الافتراضية والـ hover وحدهما.
- نسّق تركيز لوحة المفاتيح بـ
:focus-visibleوبـ outline مأخوذ من token؛ ولا تحذف الـ outline أبدًا دون بديل مرئي. - استخدم العناصر الأصلية أولًا، واتبع WAI-ARIA Authoring Practices في العناصر المخصّصة مثل التبويبات والقوائم والـ comboboxes.
- السمة
aria-disabled="true"تُبقي العنصر قابلًا للتركيز حتى تشرح سبب عدم توفّره، لكن عليك أن تمنع إجراءه بنفسك. - حين تعيش إمكانية الوصول داخل المكوّن يحصل عليها 40 فريقًا مجانًا؛ وحين تعيش في التوثيق يفوّتها معظمهم.