ARIA: وقتی HTML به‌تنهایی کافی نیست

در درس‌های قبل این فصل، بارها از ویژگی‌هایی مثل role، aria-label، aria-describedby و aria-live استفاده کردیم، بدون توضیح کامل این‌که این‌ها از کجا می‌آیند. در این درس، به‌طور مستقیم به ARIA می‌پردازیم: چیست، چه زمانی لازم است، و مهم‌تر از همه، چه زمانی نباید از آن استفاده کرد.

ARIA چیست

ARIA (مخفف Accessible Rich Internet Applications) مجموعه‌ای از ویژگی‌های HTML است که اطلاعات اضافه‌ای درباره نقش، وضعیت و رابطه عناصر، به ابزارهای کمکی مثل صفحه‌خوان می‌دهد؛ اطلاعاتی که گاهی خود تگ‌های معمولی HTML به‌تنهایی نمی‌توانند بیان کنند.

قانون اول و مهم‌ترین قانون ARIA

مستندات رسمی ARIA یک قاعده صریح دارد: اگر یک عنصر HTML معنایی (مثل button) از قبل همان رفتار و معنایی را دارد که نیاز دارید، از همان استفاده کنید؛ ARIA را فقط برای مواردی به کار ببرید که HTML استاندارد، راه‌حلی برایشان ندارد. اضافه کردن role="button" به یک button واقعی، نه‌تنها بی‌فایده، بلکه گاهی باعث رفتار متناقض می‌شود.

jsx
// غیرضروری و اشتباه
<button role="button">ارسال</button>

// درست: همین‌قدر کافی است
<button>ارسال</button>

سه دسته اصلی ویژگی‌های ARIA

ویژگی‌های ARIA معمولاً در سه گروه قرار می‌گیرند:

  • Roles: نقش یک عنصر را مشخص می‌کنند؛ مثلاً role="dialog" که در درس قبل برای Modal دیدیم.
  • Properties: ویژگی‌های نسبتاً ثابت یک عنصر را توصیف می‌کنند؛ مثل aria-label که در درس دوم دیدیم.
  • States: وضعیت فعلی و قابل‌تغییر یک عنصر را نشان می‌دهند؛ مثل aria-expanded یا aria-checked.

یک مثال از State: آکاردئون با aria-expanded

jsx
function AccordionItem({ title, children }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button
        aria-expanded={isOpen}
        onClick={() => setIsOpen(!isOpen)}
      >
        {title}
      </button>
      {isOpen && <div>{children}</div>}
    </div>
  );
}

اینجا aria-expanded مستقیم به همان State که باعث نمایش یا پنهان شدن محتوا می‌شود متصل است؛ همان‌طور که در فصل اول با Conditional Rendering دیدیم، این دو (نمایش بصری و اعلام وضعیت) باید همیشه از یک منبع واحد بیایند تا هماهنگ بمانند.

چرا این اتصال در React طبیعی است

نکته جالب این است که چون ARIA فقط چند ویژگی اضافه روی JSX است، دقیقاً مثل هر Prop دیگری با State کنترل می‌شود؛ نیازی به ابزار یا الگوی خاصی نیست. همان قوانینی که در فصل اول درباره Conditional Rendering و Expression داخل JSX دیدیم، بدون تغییر، برای ARIA هم کار می‌کنند.

خطری که زیاد استفاده کردن ARIA دارد

ARIA نادرست، می‌تواند وضعیت را بدتر از نبودنش کند؛ مثلاً اگر aria-expanded="true" روی عنصری بماند که واقعاً دیگر باز نیست، صفحه‌خوان اطلاعات اشتباه می‌دهد. به همین دلیل، یک اصل رایج در جامعه دسترس‌پذیری این است: «ARIA بد، بدتر از نبود ARIA است»؛ باید فقط جایی اضافه شود که واقعاً لازم و درست پیاده‌سازی شده باشد.

مثال قابل اجرا

یک آکاردئون با aria-expanded هماهنگ با State
import { useState } from "react";

function AccordionItem({ title, children }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button aria-expanded={isOpen} onClick={() => setIsOpen(!isOpen)}>
        {title} {isOpen ? "▲" : "▼"}
      </button>
      {isOpen && <div style={{ padding: "8px" }}>{children}</div>}
    </div>
  );
}

function App() {
  return (
    <AccordionItem title="جزئیات بیشتر">
      <p>این محتوای پنهان‌شده آکاردئون است.</p>
    </AccordionItem>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

ARIA شامل Roles، Properties و States است که اطلاعاتی فراتر از توانایی HTML استاندارد به ابزارهای کمکی می‌دهند؛ اما باید فقط وقتی استفاده شود که یک تگ معنایی موجود، همان نقش را پوشش نمی‌دهد. State های ARIA مثل aria-expanded باید همیشه مستقیم به همان State واقعی React متصل باشند، نه یک مقدار ثابت یا جدا. در درس بعدی، این اصول را در طراحی کامپوننت‌های کامل و قابل‌دسترس به کار می‌بریم.