EduFlow

الوحدة 3 · المكوّنات

الحالات وإمكانية الوصول، مبنيّة من الأساس

22 دقراءة 4 دبيئة تجريب الكود

الدرس في لمحة

ابنِ كل حالات التفاعل وسلوكيات إمكانية الوصول داخل المكوّنات افتراضيًا، بما فيها التركيز المرئي عبر ‎:focus-visible، وحالتي التعطيل والضغط، وأنماط ARIA التي تتبع WAI-ARIA Authoring Practices.

  1. 1مصفوفة الحالات
  2. 2تركيز يراه الناس
  3. 3التعطيل، بطريقتين
  4. 4أزرار التبديل، والأهداف، والأنماط

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

بدا 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 يحلّ ذلك: تطبّقه المتصفّحات حين يجب أن يكون التركيز مرئيًا، وعادةً بعد التنقّل بلوحة المفاتيح، لا بعد معظم نقرات الفأرة.

CSS
: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؛ ولأن المتصفّح لن يمنع النقر عنك، يجب أن يفعل المكوّن ذلك:

JavaScript
button.addEventListener('click', (event) => {
  if (button.getAttribute('aria-disabled') === 'true') {
    event.preventDefault();
    event.stopImmediatePropagation();
  }
});

الـ Button في النظام يفعل هذا داخليًا، فتحدّد الفرق prop واحدًا وتحصل على السلوك. وهذا هو جوهر بناء إمكانية الوصول داخل المكوّن: أربعون فريقًا لن يتذكّر كلٌّ منهم stopImmediatePropagation، لكن مكوّنًا واحدًا يستطيع.

أزرار التبديل، والأهداف، والأنماط

الزر الذي يشغّل شيئًا ويطفئه هو زر تبديل (toggle button). علّم حالته بـ aria-pressed="true" أو "false"، ونسّق هذه السمة نفسها حتى لا تتباعد الحالة المرئية عن الحالة المُعلَنة:

CSS
.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 فريقًا مجانًا؛ وحين تعيش في التوثيق يفوّتها معظمهم.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

أطلق أحد الفرق .nw-button:focus { outline: none; } لأن الحلقة «تبدو قبيحة عند النقر». ما الإصلاح الصحيح في النظام؟

السؤال 2 من 4

زر الحفظ غير متاح حتى يُختار سائق. يريد فريق المنتج tooltip يشرح السبب. أيّ نهج ينجح؟

السؤال 3 من 4

تحتاج Northwind إلى زر تبديل «Show only late shipments». أيّ markup يوصل حالته بشكل صحيح؟

السؤال 4 من 4

لماذا ترسم Northwind حلقات التركيز بـ outline بدل box-shadow؟

تدريب

بيئة تجريب الكود

ابنِ حالات التركيز والتبديل والتعطيل في المكوّن

شريط الأدوات هذا يخفي تركيز لوحة المفاتيح ويتجاهل حالاته. أصلحه في CSS وJS:

  1. احذف القاعدة :focus { outline: none } وأضف حلقة :focus-visible: ‏outline صلبًا يستخدم --nw-focus-ring-width و--nw-focus-ring-color مع outline-offset بقيمة --nw-focus-ring-offset.
  2. اجعل Show only late زر تبديل حقيقيًا: كل نقرة تقلب aria-pressed بين "true" و"false". وحين يكون مضغوطًا تكون خلفيته --nw-color-action-bg-subtle وحدّه --nw-color-action-bg.
  3. الزر Save route يحمل aria-disabled="true": يجب ألّا يحفظ عند النقر، ويستخدم نصّه --nw-color-text-disabled، ويكون المؤشر فوقه not-allowed.
<div class="toolbar">
  <button class="nw-button" type="button" id="assign">Assign driver</button>
  <button class="nw-button" type="button" id="late" aria-pressed="false">Show only late</button>
  <button class="nw-button" type="button" id="save" aria-disabled="true" aria-describedby="save-why">Save route</button>
  <p id="save-why">Select a driver before saving.</p>
</div>
<p id="log">Saved 0 times</p>جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • تنسيق التركيز يستخدم :focus-visible، ولا توجد قاعدة :focus تحذف الـ outline
  • تركيز لوحة المفاتيح يُظهر حلقة صلبة بعرض 2px بلون التركيز مع إزاحة 2px
  • النقر على Show only late يقلب aria-pressed ونمط الضغط معه
  • النقر على زر Save المعلَّم بـ aria-disabled لا يحفظ
  • يستخدم زر Save لون النص المعطّل ومؤشر not-allowed