پرتوپرتو

دسترسی‌پذیری

پرتو برای همه قابل استفاده است — WCAG AA، ناوبری با صفحه‌کلید، و پشتیبانی کامل از screen reader

دسترسی‌پذیری یک اصل بنیادی پرتو است، نه یک feature اضافی. هدف ساختن رابطی است که برای بیشترین تعداد کاربر در بیشترین شرایط کار کند — از جمله کاربرانی که از screen reader، ناوبری با صفحه‌کلید، یا نمایش با کنتراست بالا استفاده می‌کنند.

تعهد پرتو: حداقل سطح WCAG 2.1 AA در همه کامپوننت‌ها.


سطوح WCAG

سطحمعناالزام
Aپایه‌ترین الزاماتلازم
AAاستاندارد صنعتیهدف پرتو
AAAبالاترین سطحآرمانی — در برخی کامپوننت‌ها

نسبت کنتراست

نوع محتواحداقل AAهدف AAA
متن معمولی (< 18px)4.5:17:1
متن بزرگ (≥ 18px یا ≥ 14px bold)3:14.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"منوی dropdownDropdownMenu
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 قابل دسترسی هستند
  • فوکوس بصری روی هر عنصر فعال قابل مشاهده است
  • Escape dialog و 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