الوحدة 3 · متجاوب ومتين
أنماط focus تؤدي عملها
15 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
ابنِ مؤشّرات focus يراها مستخدمو لوحة المفاتيح دائمًا، باستخدام :focus-visible، وحدود outline تصمد في وضع الألوان المفروضة، وscroll padding يُبقي العنصر الذي عليه الـ focus بعيدًا عن رأس الصفحة الثابت.
- 1:focus-visible: الحلقة حين تكون مفيدة
- 2لماذا يتفوّق الـ outline على box-shadow
- 3التباين في الثيمين
- 4ضع الـ focus على البطاقة كلها
- 5لا تدع الرأس يخفي الـ focus
- 6دورك الآن
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
افتح جدول Waypoint، وأبعد الفأرة، واضغط Tab. في النسخة الأولى لن ترى شيئًا. كان ملف إعادة الضبط يحتوي على :focus { outline: none; }، أُضيفت لأن مصمّمًا لم تعجبه الحلقة التي تظهر عند النقر على الأزرار. مستخدمو لوحة المفاتيح، ومستخدمو أجهزة التبديل (switch)، وكثير من مستخدمي مكبّرات الشاشة يتنقّلون عبر الـ focus، وكانوا يتنقّلون معصوبي الأعين.
إزالة أنماط الـ focus ليست خيارًا جماليًا؛ إنها صفحة معطوبة لمجموعة كبيرة من الناس. والخبر الجيد أن CSS الحديثة تجعل كتابة أنماط focus جيدة أمرًا سهلًا.
:focus-visible: الحلقة حين تكون مفيدة
شكوى المصمّم كانت حقيقية: حلقة بعد كل نقرة فأرة تبدو خللًا. و:focus-visible تحلّ ذلك. إنها لا تطابق العنصر الذي عليه الـ focus إلا حين يرى المتصفّح أن المؤشّر مفيد:
- وصل الـ focus عبر لوحة المفاتيح: نعم.
- نقرة فأرة وضعت الـ focus على زر أو رابط: لا.
- استقبل حقل نصي الـ focus، بأيّ طريقة: نعم، لأنك تحتاج إلى رؤية المكان الذي تكتب فيه.
:root {
--focus: light-dark(#6d28d9, #c4b5fd);
}
:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}هذه القاعدة الواحدة تصلح ما أفسده ملف إعادة الضبط. إنها تنطبق على كل عنصر يمكن أن يستقبل الـ focus، فيحصل مستخدمو لوحة المفاتيح على حلقة واضحة، ولا يراها مستخدمو الفأرة على الأزرار. و:focus-visible متاحة على نطاق واسع حسب Baseline.
لماذا يتفوّق الـ outline على box-shadow
كثير من أنظمة التصميم ترسم حلقات الـ focus بـ box-shadow لأن الظلال كانت تتبع الزوايا المستديرة بينما الـ outlines لم تكن تفعل. لكن الـ outlines تتبع border-radius في كل المتصفّحات الحالية منذ 2023، ولها ثلاث مزايا:
- لا إزاحة في التخطيط. الـ outlines لا تشغل مساحة، لذا فإن وضع الـ focus على زر لا يدفع جيرانه أبدًا.
outline-offsetتضع فجوة بين العنصر والحلقة، فتبقى الحلقة ظاهرة على خلفية العنصر نفسه.- وضع الألوان المفروضة. ثيمات التباين العالي في Windows تزيل
box-shadowكليًا لكنها تُبقي الـ outlines، بعد إعادة تلوينها بلون من ألوان النظام.
التباين في الثيمين
يحتاج مؤشّر الـ focus إلى تباين لا يقل عن 3:1 مع ما يحيط به (معيار تباين العناصر غير النصية في WCAG). والبنفسجي الذي يعمل على الأبيض قد يختفي على سطح رمادي داكن. تعريف --focus بـ light-dark()، كما في الأعلى، يعطي كل ثيم لون حلقته الخاص من token واحد: بنفسجي عميق على الأسطح الفاتحة، وخزامي باهت على الداكنة.
وللـ components التي تجلس على خلفيات لا يمكن توقّعها (صورة الـ hero، أو شارة مسار ملوّنة)، الحلقة ثنائية اللون هي الأكثر أمانًا: outline بلون الـ focus مع هالة متباينة داخلها مباشرةً:
.hero :focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
box-shadow: 0 0 0 2px var(--surface); /* halo between element and ring */
}الظلّ هنا زخرفة فوق outline حقيقي، لذا يظل مستخدمو الألوان المفروضة يحصلون على الـ outline.
ضع الـ focus على البطاقة كلها
بطاقات الجلسات في Waypoint قابلة للنقر: العنوان رابط، ممدود بواسطة pseudo-element ليغطّي البطاقة. عندها تظهر الحلقة حول نص العنوان فقط، وهذا يبدو غريبًا. انقلها إلى البطاقة بـ :has():
.session-card:has(a:focus-visible) {
outline: 2px solid var(--focus);
outline-offset: 4px;
}
.session-card a:focus-visible {
outline: none; /* the card shows the ring instead */
}إزالة حلقة الرابط نفسه مقبولة هنا، لأن حلقة البطاقة تحلّ محلّها كلما كان على الرابط focus ظاهر. وهذا هو النوع الوحيد من outline: none الذي ينبغي أن تطلقه: نوعٌ يغطّيه مؤشّر آخر.
لا تدع الرأس يخفي الـ focus
أضافت WCAG 2.2 معيار Focus Not Obscured (الـ focus غير محجوب): لا يجوز أن يختفي العنصر الذي عليه الـ focus كليًا خلف محتوى وضعه المطوّر فوقه، مثل رأس صفحة ثابت أو شريط ملفات تعريف الارتباط (cookies). رأس Waypoint ثابت وارتفاعه 64px، وحين يتنقّل المستخدم إلى الخلف، يمرّر المتصفّح البطاقة التي عليها الـ focus إلى أعلى الـ viewport، تحت الرأس مباشرةً.
html {
scroll-padding-top: 64px;
}الـ scroll padding تخبر المتصفّح بأن جزءًا من مساحة العرض في حاوية التمرير مغطّى. والتمرير الناتج عن الـ focus والروابط الداخلية (anchors) وscrollIntoView() كلها تحترمها. وإن تغيّر ارتفاع الرأس، فخزّنه في custom property واستخدمها في المكانين.
دورك الآن
في صفحة التمرين ملف إعادة الضبط السيئ. استبدله بحلقة :focus-visible، وانقل الحلقة إلى البطاقة حين يكون الـ focus على رابطها، وأضف scroll padding للرأس ذي الـ 64px. وبهذا ينتهي القسم المتجاوب؛ وبعده ستضيف الحركة، بدءًا بالـ transitions والـ keyframes واحترام إعدادات تقليل الحركة.
أهم النقاط
:focus-visibleتطابق حين يقرّر المتصفّح أن مؤشّر الـ focus مفيد، وهذا عادةً عند التنقّل بلوحة المفاتيح، فتستطيع إظهار حلقة واضحة دون إظهارها مع كل نقرة فأرة.- استخدم
outlineمعoutline-offsetلحلقات الـ focus؛ فهي لا تغيّر التخطيط، وتتبعborder-radius، وتبقى ظاهرة في وضع الألوان المفروضة (forced colors) في Windows حيث تختفيbox-shadow. - عرّف لون الـ focus بـ
light-dark()ليحصل كل ثيم على لون حلقة خاص به، ثم تحقّق من أن تباينه مع السطح لا يقل عن 3:1 في الثيمين. scroll-padding-topعلى حاوية التمرير تمنع رأس الصفحة الثابت (sticky) من تغطية العنصر الذي استقبل الـ focus للتوّ.