بناء تطبيقات ويب يمكن الوصول إليها ليس مجرد امتثال — بل هو إنشاء تجارب تعمل للجميع. ومع كون WCAG 2.2 الآن المعيار، يغطّي هذا الدليل استراتيجيات تطبيق عملية للمطوّرين.
فهم WCAG 2.2
تُنظَّم WCAG 2.2 حول أربعة مبادئ (POUR):
- قابل للإدراك (Perceivable): يجب تقديم المعلومات بطرق يمكن للمستخدمين إدراكها
- قابل للتشغيل (Operable): يجب أن تكون مكوّنات الواجهة قابلة للتشغيل من جميع المستخدمين
- مفهوم (Understandable): يجب أن تكون المعلومات والتشغيل مفهومة
- متين (Robust): يجب أن يكون المحتوى متيناً بما يكفي للتقنيات المساعِدة
أنماط أساسية لإمكانية الوصول
HTML الدلالي
<!-- Bad: div soup -->
<div class="header">
<div class="nav">
<div class="nav-item">Home</div>
</div>
</div>
<!-- Good: semantic elements -->
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
النماذج القابلة للوصول
<form>
<div class="form-group">
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-describedby="email-help email-error"
aria-invalid="true"
required
>
<p id="email-help" class="help-text">
We'll never share your email.
</p>
<p id="email-error" class="error" role="alert">
Please enter a valid email address.
</p>
</div>
<button type="submit">Subscribe</button>
</form>
التنقّل بلوحة المفاتيح
// Custom dropdown with keyboard support
function Dropdown({ options, onChange }) {
const [isOpen, setIsOpen] = useState(false);
const [focusIndex, setFocusIndex] = useState(0);
const handleKeyDown = (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setFocusIndex(i => Math.min(i + 1, options.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
setFocusIndex(i => Math.max(i - 1, 0));
break;
case 'Enter':
case ' ':
e.preventDefault();
onChange(options[focusIndex]);
setIsOpen(false);
break;
case 'Escape':
setIsOpen(false);
break;
}
};
return (
<div
role="listbox"
tabIndex={0}
onKeyDown={handleKeyDown}
aria-activedescendant={\`option-\${focusIndex}\`}
>
{options.map((option, i) => (
<div
key={option.value}
id={\`option-\${i}\`}
role="option"
aria-selected={i === focusIndex}
>
{option.label}
</div>
))}
</div>
);
}
الألوان والتباين
تتطلّب WCAG 2.2:
- النص العادي: نسبة تباين 4.5:1
- النص الكبير (18pt+): نسبة تباين 3:1
- مكوّنات الواجهة: 3:1 مقابل الألوان المجاورة
/* CSS custom properties for accessible colors */
:root {
--text-primary: #1a1a1a; /* 15:1 on white */
--text-secondary: #595959; /* 7:1 on white */
--background: #ffffff;
--accent: #0066cc; /* 5.9:1 on white */
--error: #d32f2f; /* 4.8:1 on white */
}
أنماط ARIA
المناطق الحيّة (Live Regions)
<!-- Announce dynamic content changes -->
<div aria-live="polite" aria-atomic="true">
{{ statusMessage }}
</div>
<!-- For urgent alerts -->
<div role="alert">
Your session will expire in 2 minutes.
</div>
النوافذ الحوارية (Modal Dialogs)
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title">Confirm deletion</h2>
<p id="dialog-description">
This action cannot be undone.
</p>
<button onClick={onConfirm}>Delete</button>
<button onClick={onCancel}>Cancel</button>
</div>
أدوات الاختبار
- axe DevTools: امتداد متصفّح للاختبار الآلي
- WAVE: أداة تقييم إمكانية الوصول للويب
- Lighthouse: مدمج في Chrome DevTools
- قارئات الشاشة: NVDA (ويندوز)، VoiceOver (ماك/iOS)
// Automated testing with jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form is accessible', async () => {
const { container } = render(<ContactForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
إمكانية الوصول رحلة لا وجهة. ابدأ بالأساسيات، واختبر بانتظام، وحسّن باستمرار.
التعليقات (0)
اترك تعليقًا
لا توجد تعليقات بعد. كن أول من يشارك أفكاره!