دسترسیپذیری
پرتو برای همه قابل استفاده است — WCAG AA، ناوبری با صفحهکلید، و پشتیبانی کامل از screen reader
دسترسیپذیری یک اصل بنیادی پرتو است، نه یک feature اضافی. هدف ساختن رابطی است که برای بیشترین تعداد کاربر در بیشترین شرایط کار کند — از جمله کاربرانی که از screen reader، ناوبری با صفحهکلید، یا نمایش با کنتراست بالا استفاده میکنند.
تعهد پرتو: حداقل سطح WCAG 2.1 AA در همه کامپوننتها.
سطوح WCAG
| سطح | معنا | الزام |
|---|---|---|
| A | پایهترین الزامات | لازم |
| AA | استاندارد صنعتی | هدف پرتو |
| AAA | بالاترین سطح | آرمانی — در برخی کامپوننتها |
نسبت کنتراست
| نوع محتوا | حداقل AA | هدف AAA |
|---|---|---|
| متن معمولی (< 18px) | 4.5:1 | 7:1 |
| متن بزرگ (≥ 18px یا ≥ 14px bold) | 3:1 | 4.5:1 |
| عناصر UI و گرافیک | 3:1 | — |
// درست — رنگهای سمانتیک که کنتراست آنها تضمین شده است
<div className="bg-background text-foreground">متن اصلی</div>
<p className="bg-surface-100 text-light">متن توضیحی</p>
// نادرست — ترکیب با کنتراست ناکافی
<div className="bg-background text-foreground/30">خوانایی سخت</div>ناوبری با صفحهکلید
هر عملی که با ماوس انجام میشود باید با صفحهکلید نیز قابل انجام باشد.
کلیدهای استاندارد
| کلید | عملکرد |
|---|---|
Tab | رفتن به عنصر تعاملی بعدی |
Shift + Tab | رفتن به عنصر تعاملی قبلی |
Enter | فعالسازی دکمه، لینک، یا آیتم منو |
Space | فعالسازی checkbox، radio، یا دکمه |
Escape | بستن dialog، dropdown، یا popover |
↑ ↓ | ناوبری در منو، select، یا listbox |
← → | ناوبری در tab list، slider، یا date picker |
Home / End | رفتن به اولین / آخرین آیتم |
فوکوس بصری
همه عناصر تعاملی باید در حالت focus-visible یک نشانه بصری واضح داشته باشند:
// پرتو از استایل focus یکنواخت استفاده میکند
<button className="inset-focus">
دکمه
</button>
// برای عناصر سفارشی
<div
tabIndex={0}
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand"
onKeyDown={(e) => e.key === 'Enter' && handleAction()}
>
عنصر سفارشی
</div>مدیریت فوکوس در Dialog
وقتی dialog باز میشود، فوکوس باید به داخل آن منتقل شود. وقتی بسته میشود، فوکوس باید به trigger اولیه برگردد:
// کامپوننت Dialog پرتو این رفتار را به صورت خودکار مدیریت میکند
<Dialog>
<DialogTrigger>
<Button>باز کردن</Button>
</DialogTrigger>
<DialogContent>
{/* فوکوس به اینجا میآید هنگام باز شدن */}
<DialogTitle>عنوان</DialogTitle>
</DialogContent>
</Dialog>پشتیبانی از Screen Reader
متن برای screen reader (sr-only)
برای عناصری که فقط بصری معنا دارند، متن توضیحی اضافه کنید:
// ستون جدول بدون عنوان بصری
<TableHead>
<span className="sr-only">اقدامات</span>
</TableHead>
// دکمه آیکونی بدون متن
<button aria-label="حذف اینفلوئنسر">
<Trash2 className="h-4 w-4" />
</button>
// لینک با متن مبهم
<a href="/influencer/123">
مشاهده
<span className="sr-only"> پروفایل علی احمدی</span>
</a>نقشهای ARIA
| ویژگی | کاربرد | مثال |
|---|---|---|
role="alert" | پیامهای مهم که باید فوری اعلام شوند | پیام خطای فرم |
role="dialog" | محتوای dialog | کامپوننت Dialog |
role="menu" | منوی dropdown | DropdownMenu |
role="listbox" | لیست قابل انتخاب | Select، Autocomplete |
aria-expanded | باز/بسته بودن عنصر | Accordion، Dropdown |
aria-selected | آیتم انتخاب شده | Tab، ListBox |
aria-disabled | عنصر غیرفعال | دکمه disabled |
aria-label | نام عنصر بدون متن بصری | دکمه آیکونی |
aria-labelledby | ارجاع به عنصر عنوان | Dialog به DialogTitle |
aria-describedby | ارجاع به توضیحات | Input به FormMessage |
aria-live | ناحیه محتوای پویا | اعلان خطا، نتایج جستجو |
// اعلان پویا — نتایج جستجو
<div aria-live="polite" aria-atomic="true" className="sr-only">
{searchResults.length} نتیجه یافت شد
</div>
// خطای فرم — فوری اعلام میشود
<div role="alert" aria-live="assertive">
<FormMessage />
</div>
// لیست loading
<div aria-busy={isLoading} aria-label="در حال بارگذاری اینفلوئنسرها">
{isLoading ? <ListSkeleton /> : <InfluencerList />}
</div>برچسبهای فرم
همیشه label را با input مرتبط کنید. بدون این ارتباط، screen readerها نمیتوانند فیلد را معرفی کنند:
// درست — با htmlFor و id
<label htmlFor="username">نام کاربری</label>
<input id="username" aria-describedby="username-hint" />
<p id="username-hint" className="text-muted text-xs">
حداقل ۳ کاراکتر
</p>
// درست — با کامپوننتهای پرتو
<FormField
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>ایمیل</FormLabel>
<FormControl>
<Input aria-describedby="email-error" {...field} />
</FormControl>
<FormMessage id="email-error" /> {/* role="alert" دارد */}
</FormItem>
)}
/>
// فیلد اجباری
<FormLabel>
ایمیل
<span aria-hidden="true" className="text-destructive ms-1">*</span>
<span className="sr-only"> (اجباری)</span>
</FormLabel>اندازه اهداف لمس
برای دستگاههای لمسی، حداقل اندازه هدف 44×44px (توصیه WCAG) یا 48×48px (توصیه Google Material):
// درست — هدف لمس کافی
<button className="h-11 min-w-11 px-4">دکمه</button>
// نادرست — هدف لمس خیلی کوچک
<button className="h-6 w-6">x</button>
// برای آیکونهای کوچک، ناحیه کلیک را بزرگ کنید
<button className="h-11 w-11 flex items-center justify-center">
<X className="h-4 w-4" />
</button>RTL و دسترسیپذیری
دو attribute روی <html> برای کارکرد صحیح screen reader در فارسی الزامی هستند:
<html lang="fa" dir="rtl">lang="fa"— screen reader را به حالت خواندن فارسی تغییر میدهدdir="rtl"— ترتیب خواندن محتوا را درست میکند
بدون lang="fa"، screen readerها ممکن است فارسی را به اشتباه تلفظ کنند یا اصلاً نخوانند.
تصاویر و گرافیک
// تصویر محتوایی — باید alt توصیفی داشته باشد
<img src="/avatar.jpg" alt="تصویر پروفایل علی احمدی" />
// تصویر تزئینی — alt خالی (از screen reader پنهان میشود)
<img src="/decorative.svg" alt="" />
// آیکون با متن — آیکون را از screen reader پنهان کنید
<button>
<Plus aria-hidden="true" className="me-2" />
اضافه کردن اینفلوئنسر
</button>
// نمودار — همیشه خلاصه متنی اضافه کنید
<div>
<LineChart data={data} />
<p className="sr-only">
نمودار روند ذکرها در ۳۰ روز گذشته. بیشترین ذکر در ۱۵ اسفند با ۲۳۴ مورد.
</p>
</div>چکلیست
قبل از commit کردن هر کامپوننت یا صفحه جدید:
صفحهکلید:
- همه عناصر تعاملی با
Tabقابل دسترسی هستند - فوکوس بصری روی هر عنصر فعال قابل مشاهده است
Escapedialog و dropdown را میبندد- ترتیب Tab منطقی است (از بالا به پایین، از راست به چپ در RTL)
Screen Reader:
- تصاویر محتوایی
altدارند - دکمههای آیکونی
aria-labelدارند - فیلدهای فرم با label مرتبط هستند
- پیامهای خطا با
role="alert"اعلام میشوند - نمودارها
sr-onlyخلاصه دارند
رنگ و کنتراست:
- متن معمولی حداقل کنتراست 4.5:1 دارد
- از رنگ به تنهایی برای انتقال معنا استفاده نشده (مثلاً خطا فقط با قرمز نیست)
عمومی:
lang="fa" dir="rtl"روی<html>وجود دارد- اندازه هدف لمس حداقل 44px است
ابزارهای تست
| ابزار | کاربرد |
|---|---|
| axe DevTools | افزونه مرورگر — تشخیص خودکار مشکلات |
| Colour Contrast Analyser | بررسی دستی نسبت کنتراست |
| NVDA (Windows) | screen reader رایگان برای تست |
| VoiceOver (macOS/iOS) | screen reader پیشفرض Apple |
| Chrome DevTools → Accessibility | نمایش accessibility tree |