IlmHamroh
JavaScript Full-stack/18-qism. React chuqur39/42-dars18 daqiqa
Mundarija (30)

React'da accessibility asoslari: semantik JSX, label, aria va avtomatik tekshiruv

Qisqacha: React accessibility'ni o'zi buzmaydi ham, tuzatmaydi ham — u siz yozgan HTML'ni chizadi. Shuning uchun asosiy qoida HTML'dagidek: to'g'ri teg (<button>, <nav>, <label>), har boshqaruv elementining nomi bor, rasmlarda alt, holat esa aria-* bilan (aria-expanded, aria-invalid). JSX'dagi farqlar kichik: for o'rniga htmlFor, aria-* esa chiziqcha bilan qoladi. Xatolarni uch qavatda ushlaysiz: kod yozishda (eslint-plugin-jsx-a11y), brauzerda (axe) va qo'lda — klaviatura va ekran o'quvchi (NVDA) bilan.

Bu darsda

  • <div onClick> nega tugma emasligini jonli ko'rasiz va uni <button> bilan almashtirasiz.
  • Har boshqaruv elementiga nom berasiz: <label htmlFor>, useId, aria-label, aria-labelledby.
  • aria-* atributlarini React'da to'g'ri yozasiz va qachon ARIA kerak emasligini bilasiz.
  • Uch qavatli tekshiruvni sozlaysiz: lint, axe va qo'lda (Bobur — klaviatura, Malika — NVDA).
  • vazifalar ni axe-core 4.13.0 bilan olti holatda o'lchaysiz va Malika "ko'radigan" daraxtni tekshirasiz.

Oldin bilishingiz kerak: Accessibility daraxti, ARIA asoslari, Formalar I, useId.

1. Nega bu kerak?

Kursdagi ikki do'stimizni eslang. Malika ko'rmaydi — kompyuterda NVDA ekran o'quvchisi bilan ishlaydi, u sahifani ovoz chiqarib o'qiydi. Bobur sichqoncha ishlata olmaydi — faqat klaviatura: Tab, Enter, Space, strelkalar.

Sardor «Bahor» buyurtma sahifasini React'da yozdi. Ko'rinishi chiroyli. Malika sahifani ochdi va eshitdi: "tugma, tugma, tahrirlanadigan matn, tugma". Qaysi tugma nima qiladi — noma'lum. Bobur esa Tab bosib, "Savatga qo'shish" ga umuman yetib bormadi: u <div onClick> edi.

HTML qismida accessibility (qisqasi a11y — "a", 11 harf, "y") nima ekanini va kimga kerakligini o'rgandik. React'da qoidalar o'sha. Lekin komponentlar yangi xavf qo'shadi: bitta noto'g'ri Button komponenti butun ilovadagi 50 ta tugmani buzadi. Teskarisi ham rost — bitta to'g'ri komponent 50 joyni tuzatadi.

Hayotiy o'xshatish: binoga kirish. Zinapoya hamma uchun, pandus esa nogironlar aravachasi uchun. Lekin pandus faqat ular uchun emas: aravacha, chamadon, velosiped bilan kelganlarga ham qulay. Accessibility ham shunday: klaviatura bilan tez ishlaydigan dasturchi, telefonda bir qo'li band odam, quyosh ostida ekranni zo'rg'a ko'radigan haydovchi — hammasi foyda ko'radi.

2. Semantik JSX

2.1 <div onClick> — tugma emas

Eng ko'p uchraydigan a11y xatosi. Ikkalasini yonma-yon sinaymiz:

jsx
import { useRef } from "react";

export default function App() {
  const divRef = useRef(null);
  const buttonRef = useRef(null);
  function check() {
    divRef.current.focus();
    const divOk = document.activeElement === divRef.current;
    console.log("div fokusda:", divOk);
    buttonRef.current.focus();
    const ok = document.activeElement === buttonRef.current;
    console.log("button fokusda:", ok);
  }
  return (
    <div>
      <div ref={divRef} onClick={() => console.log("savatga")}>
        Savatga (div)
      </div>
      <button ref={buttonRef} onClick={() => console.log("savatga")}>
        Savatga (button)
      </button>
      <button onClick={check}>Tekshirish</button>
    </div>
  );
}

Konsolda:

text
div fokusda: false
button fokusda: true

Ekranda:

text
Savatga (div)
Savatga (button)Tekshirish

div ga fokus tushmaydi — Bobur Tab bilan unga yetib bormaydi. Bu bitta muammo. To'liq ro'yxat:

<div onClick> <button>
Tab bilan fokus
Enter va Space bosish (brauzer click beradi)
Ekran o'quvchi roli "matn" "tugma"
disabled
formada — type bo'yicha yuboradi

div ni "tuzatish" mumkin: role="button", tabIndex={0}, onKeyDown da Enter va Space. Lekin bu — beshta qo'shimcha narsa, bittasini unutish oson. <button> ularning hammasini bepul beradi. ARIA'ning birinchi qoidasi shu: native element bor bo'lsa — uni ishlating (ARIA asoslari).

2.2 Qaysi teg qachon

Ma'no Teg Noto'g'ri tanlov
amal bajaradi <button> <div onClick>, <a href="#">
boshqa sahifaga olib boradi <a href> <button onClick={navigate}>
sahifa qismlari <header>, <nav>, <main>, <footer> <div className="header">
sarlavhalar daraxti <h1>…<h6> (tartib bilan) <p className="katta">
ro'yxat <ul> + <li> <div> lar
jadval ma'lumot <table> + <th scope> <div> grid

Bir nozik joy: React komponenti ko'pincha <div> qaytaradi, chunki "nimadir o'rash kerak". Har qo'shimcha <div> zararsiz, lekin semantikasiz. Ortiqcha o'ram kerak bo'lmasa — <>…</> (Fragment). <ul> ichida <li> dan boshqa narsa (masalan, <div> o'ram) — ekran o'quvchi uchun ro'yxat buziladi. axe buni list qoidasi bilan ushlaydi.

3. Har boshqaruv elementining nomi

3.1 Nom (accessible name) nima

Accessibility daraxti darsida ko'rdik: brauzer DOM'dan alohida daraxt quradi, ekran o'quvchi uni o'qiydi. Har tugun uchun uch narsa: rol (tugma, maydon), nom (nimaligi), holat (bosilgan, ochiq, xato). Rol tegdan keladi. Nom esa siz bergan narsadan:

flowchart LR
  J["JSX: &lt;button&gt;Saqlash&lt;/button&gt;"] --> D["DOM"]
  D --> A["a11y daraxti: rol, nom, holat"]
  A --> N["NVDA: Saqlash, tugma"]
  A --> X["axe: nomi bormi?"]

Diagrammaning o'ng tomoniga qarang: Malika ham, axe ham bir xil daraxtni o'qiydi. axe "nomi yo'q" desa — Malika ham hech narsa eshitmaydi. Nom manbalari, ustunlik tartibida:

  1. aria-labelledby — boshqa elementning matni (id bo'yicha).
  2. aria-label — to'g'ridan-to'g'ri satr.
  3. <label> (maydonlar uchun), alt (rasmlar uchun).
  4. Elementning ichidagi matn (tugma, havola).
  5. title — oxirgi chora, ishonchsiz.

3.2 Maydonlar: <label htmlFor> va useId

JSX'da for — JavaScript'ning band so'zi, shuning uchun atribut nomi htmlFor (JSX aslida JavaScript). Komponent bir sahifada bir necha marta ishlatilsa, id takrorlanadi — useId buni hal qiladi:

jsx
import { useId } from "react";

function Field({ label }) {
  const id = useId();
  return (
    <p>
      <label htmlFor={id}>{label}</label> <input id={id} />
    </p>
  );
}

export default function App() {
  function check() {
    for (const input of document.querySelectorAll("input")) {
      console.log(input.id, "→", input.labels[0]?.textContent);
    }
  }
  return (
    <form>
      <Field label="Ismingiz" />
      <Field label="Telefon" />
      <button type="button" onClick={check}>
        Tekshirish
      </button>
    </form>
  );
}

Konsolda:

text
_r_0_ → Ismingiz
_r_1_ → Telefon

Ekranda:

text
Ismingiz
Telefon
Tekshirish

input.labels — brauzer o'zi bog'lagan yorliqlar. Ikkalasida ham to'g'ri yorliq — useId ikki xil id berdi. Yorliqni bosganda maydon fokuslanadi: telefonda kichik maydonga tegish qiyin, katta yorliqqa — oson.

placeholder — yorliq emas. U yozish boshlanganda yo'qoladi, kontrasti past, ba'zi ekran o'quvchilar uni o'qimaydi. vazifalar dagi kirish formasida (42-dars) har maydonda <label> bor.

3.3 Ikonka tugma: aria-label

Matnsiz tugma (, , ) — eng ko'p "nomsiz" xato. Ikki yo'l:

jsx
// ✅ aria-label — nom ko'rinmaydi, faqat eshitiladi
<button aria-label="Savatni yopish" onClick={close}>✕</button>

// ✅ ko'rinmas matn — CSS bilan yashirilgan, lekin daraxtda bor
<button onClick={close}>
  <span aria-hidden="true">✕</span>
  <span className="sr-only">Savatni yopish</span>
</button>

Ikkinchi variantda aria-hidden="true" "" belgisini daraxtdan olib tashlaydi. Aks holda NVDA uni "ko'paytirish belgisi" deb o'qishi mumkin. Klass sr-only — ekranda ko'rinmaydigan, lekin o'qiladigan matn klassi (CSS'da accessibility). React + TS IV da vazifalar dagi Tugma ga "nomi bo'lishi shart" qoidasini tur bilan qo'ydik: matnsiz va aria-label siz tugma — tsc xatosi.

3.4 Rasmlar: alt

Rasm alt
ma'lumot beradi (taom surati menyuda) alt="Osh — sabzi, go'sht, guruch"
bezak (fon naqshi) alt="" — bo'sh, lekin bor
tugma ichida yagona narsa amalni yozing: alt="Savatga qo'shish"

alt umuman yo'q bo'lsa, ekran o'quvchi fayl nomini o'qiydi: "osh underscore 2 dot webp". alt="" esa "bu rasmni o'tkazib yubor" degani. React alt siz <img> uchun ogohlantirish bermaydi — buni lint ushlaydi (keyingi bo'lim).

Tekshirib ko'ring: Tugmada ham matn ("Saqlash"), ham aria-label="Buyurtmani saqlash" bor. Malika nimani eshitadi?

Javob

"Buyurtmani saqlash, tugma". aria-label ichki matndan ustun («Nom (accessible name) nima» bo'limidagi tartib). Bu qulay, lekin xavfli: ko'zi ko'radigan foydalanuvchi "Saqlash" ni ko'radi, ovozli boshqaruv (Voice Control) ishlatadigan esa "Saqlash" deb aytadi — va nom mos kelmasa, tugma topilmaydi. Qoida: aria-label ko'rinadigan matnni o'z ichiga olsin.

4. aria-* React'da

4.1 Yozilishi

React'da deyarli hamma atribut camelCase (tabIndex, className, onClick). aria-* va data-* — istisno: ular HTML'dagidek, chiziqcha bilan. Boolean qiymat satrga aylanadi:

jsx
import { useState } from "react";

export default function App() {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button
        aria-expanded={open}
        aria-controls="menyu-royxat"
        onClick={() => {
          setOpen(!open);
          const b = document.querySelector("button");
          const oldin = b.getAttribute("aria-expanded");
          console.log("bosishdan oldin:", oldin);
        }}
      >
        Menyu
      </button>
      <ul id="menyu-royxat" hidden={!open}>
        <li>Osh</li>
        <li>Manti</li>
      </ul>
    </div>
  );
}

Konsolda:

text
bosishdan oldin: false

Ekranda:

text
Menyu
Osh
Manti

aria-expanded={false} DOM'da "false" satri bo'lib yozildi — ARIA aynan shuni kutadi. Bosilgach — "true" va ro'yxat ochildi. Malika "Menyu, tugma, yig'ilgan" va keyin "yoyilgan" deb eshitadi.

Eng ko'p ishlatiladigan holat atributlari:

Atribut Ma'no Misol
aria-expanded ochiq/yopiq menyu, akkordeon tugmasi
aria-pressed bosilgan/bosilmagan "Sevimli" tugmasi
aria-invalid maydonda xato forma validatsiyasi
aria-describedby qo'shimcha izoh id'si maydon ostidagi xato matni
aria-current="page" joriy sahifa navigatsiyadagi havola
aria-hidden="true" daraxtdan yashirish bezak ikonka
aria-busy yangilanmoqda ro'yxat yuklanayotganda

aria-busy ni vazifalar da ko'rgansiz: qidiruvda ro'yxat kechiktirib chizilayotganda (useDeferredValue).

4.2 Forma xatosi: aria-invalid + aria-describedby

Xato faqat qizil rang bilan ko'rsatilsa, Malika uni bilmaydi. To'g'ri yo'l — maydonni xato bilan bog'lash:

jsx
import { useId, useState } from "react";

export default function App() {
  const id = useId();
  const [error, setError] = useState(null);
  return (
    <form
      noValidate
      onSubmit={(e) => {
        e.preventDefault();
        setError("Telefon raqamini yozing");
      }}
    >
      <label htmlFor={id}>Telefon</label>{" "}
      <input
        id={id}
        aria-invalid={error !== null ? true : undefined}
        aria-describedby={error !== null ? `${id}-xato` : undefined}
      />
      <p id={`${id}-xato`} role="status">
        {error}
      </p>
      <button>Buyurtma</button>
    </form>
  );
}

Ekranda:

text
Telefon
Telefon raqamini yozing
Buyurtma

Uch narsa birga ishlaydi:

  • aria-invalid — maydon holati "xato". Xato yo'q bo'lsa — undefined, ya'ni atribut umuman yo'q (false emas — tozaroq).
  • aria-describedby — maydonga fokus tushganda Malika nomdan keyin xato matnini ham eshitadi: "Telefon, tahrirlanadigan matn, xato, Telefon raqamini yozing".
  • role="status" — <p> "jonli hudud" (live region): matn o'zgarganda NVDA uni o'zi o'qiydi, fokus siljimasa ham. <p> doim DOM'da turadi, faqat ichi o'zgaradi — jonli hudud oldindan bo'lishi kerak. Batafsil — keyingi darsda.

4.3 ARIA kerak bo'lmagan joy

ARIA — kuchli, lekin xavfli. Noto'g'ri ARIA, ARIA yo'qligidan yomonroq: <button role="link"> — ekran o'quvchi "havola" deydi, Enter bosilsa esa tugma ishlaydi. Uch qoida:

  1. Native element bo'lsa — ARIA emas, teg: <button>, <nav>, <dialog>, details tegi.
  2. Native semantikani o'zgartirmang: <h2 role="button"> — yo'q, <h2><button>…</button></h2> — ha.
  3. Hamma interaktiv ARIA element klaviatura bilan ishlashi shart. role="menu" yozdingizmi — strelkalar ham sizning ishingiz. vazifalar dagi Menyu (Headless komponentlar) shuni APG naqshi bo'yicha qilgan.

5. Avtomatik tekshiruv — uch qavat

5.1 Lint: eslint-plugin-jsx-a11y

Eng erta qavat — kod yozayotganda. eslint-plugin-jsx-a11y JSX'ni statik tekshiradi: alt siz <img>, onClick li <div> (klaviaturasiz), <label> siz maydon, noto'g'ri aria-* nomi. Flat config'da (ESLint I) ulanishi:

js
// eslint.config.js — o'z loyihangizda
import jsxA11y from "eslint-plugin-jsx-a11y";

export default [
  // …
  {
    files: ["src/**/*.{jsx,tsx}"],
    ...jsxA11y.flatConfigs.recommended,
  },
];

O'rnatishdan oldin paketning peerDependencies ida ESLint versiyangiz borligini tekshiring (Paket tanlash va baholash). Bu yerda bu muhim. 2026-yil oktyabr holatida npm view eslint-plugin-jsx-a11y version peerDependencies shuni ko'rsatadi: oxirgi versiya — 6.10.2 (2024-yil oktyabr), ruxsat etilgan ESLint — ^3 … ^9. ESLint 10 ro'yxatda yo'q. vazifalar dagi ESLint 10.12.0 bilan npm install to'xtaydi: ERESOLVE unable to resolve dependency tree (sinab ko'rdik). Xatoni --legacy-peer-deps bilan chetlab o'tish mumkin, lekin plagin ESLint 10 da ishlashiga hech kim kafolat bermaydi. Shuning uchun avval paketning GitHub sahifasida yangi versiya chiqqanini tekshiring. Lint'ning chegarasi: u faqat kodni ko'radi. <Tugma> ichida nima chizilishini, aria-labelledby dagi id haqiqatan borligini, rang kontrastini — bilmaydi.

vazifalar da bu plagin yo'q. U yerdagi himoya boshqa: tur darajasida (Tugma nomi — 37-dars) va ish vaqtida — axe va klaviatura ssenariysi. Ikki yondashuv bir-birini to'ldiradi.

5.2 Brauzerda: axe

axe-core — Deque kompaniyasining ochiq kodli a11y tekshiruvchisi. U tayyor sahifadagi haqiqiy a11y daraxtini tekshiradi: nom, kontrast, sarlavhalar tartibi, ARIA'ning to'g'riligi. Uni uch xil ishlatasiz:

  • axe DevTools — Chrome kengaytmasi: DevTools'da "axe" paneli, "Scan" tugmasi.
  • Lighthouse — Chrome'ga o'rnatilgan, "Accessibility" bo'limi — ichida o'sha axe-core.
  • Testlarda — Playwright yoki Vitest bilan avtomatik (Accessibility testlari).

axe ham hammasini topmaydi. Odatda aytiladi: avtomatik vositalar a11y muammolarining taxminan uchdan birini ushlaydi. Qolgani — mantiq: fokus qayerga ketdi, xabar tushunarlimi, tartib to'g'rimi. Buni faqat odam sinaydi.

5.3 Qo'lda: Bobur va Malika

Klaviatura (Bobur). Sichqonchani chetga suring. Tab — oldinga, Shift+Tab — orqaga, Enter/Space — bosish, Esc — yopish. Har qadamda so'rang: fokus ko'rinyaptimi? Tartib mantiqiymi? Hamma joyga yetib bordimmi? Dialogdan chiqa olamanmi?

Ekran o'quvchi (Malika). Windows'da — NVDA (bepul, nvaccess.org). Asosiy tugmalar:

Tugma Nima qiladi
Ctrl o'qishni to'xtatadi
↓ keyingi qatorni o'qiydi
Tab keyingi interaktiv element
H keyingi sarlavha
Insert+F7 sahifadagi havolalar, sarlavhalar ro'yxati

Birinchi marta g'alati tuyulishi tabiiy — NVDA tez gapiradi. 10 daqiqa yetadi: o'z sahifangizni ko'zingizni yumib "tinglang". macOS'da — VoiceOver (Cmd+F5), telefonda — TalkBack (Android) va VoiceOver (iOS).

Tekshirib ko'ring: axe 0 buzilish ko'rsatdi. Sahifa accessible deyish mumkinmi?

Javob

Yo'q, faqat "avtomatik topiladigan xatolar yo'q" deyish mumkin. axe fokus tartibini, dialog yopilganda fokus qaytishini, xabar ma'nosini, klaviatura tuzog'ini (fokus chiqolmaydigan joy) tekshirmaydi. Shuning uchun vazifalar da axe yonida klaviatura ssenariysi ham bor — «Vazifalar qadami» bo'limida.

6. Vazifalar qadami: axe va Malika daraxti

6.1 Olti holat, axe-core 4.13.0

feat/a11y-otish branch'i — ikki darslik qadam, commit keyingi darsda. Avval o'lchov: bugungi holat qanchalik accessible?

Kanon axe-core 4.13.0 ni (Lighthouse 13.5.0 ichidagi o'sha versiya) build qilingan ilovaga Chrome 155 orqali yukladi. Qoidalar to'plami — WCAG 2.0/2.1/2.2 A va AA hamda "best practice". Olti holat tekshirildi — har birida axe sahifaning o'sha paytdagi ko'rinishini oladi:

axe-core 4.13.0: o'tgan qoidalar (buzilish — har holatda 0)
  • asosiy (3 vazifa)36 qoida
  • tahrir rejimi36 qoida
  • o'chirish dialogi17 qoida
  • statistika + menyu44 qoida
  • qorong'i tema36 qoida
  • 300 vazifa (virtual)37 qoida

Manba: vazifalar holat 38, natijalar/40-axe-oldin.txt — Chrome 155.0.8059.40, 2026-10-13

Grafikda buzilishlar yo'q — hammasi 0. Ustunlar tekshirilgan qoidalar soni: dialog showModal bilan ochilganda sahifaning qolgan qismi inert bo'ladi — tekshiriladigan elementlar kamayadi, shuning uchun 17. Statistika paneli va menyu ochiq holatda — eng ko'p: jadval, menyu ARIA'si qo'shiladi.

Nega 0? Chunki vazifalar 4-qismdan beri semantik HTML bilan yozilgan. 17-qismda React'ga ko'chganda teglar o'zgarmadi, 18-qismda Menyu APG naqshi bilan qurildi, 37-darsda Tugma nomi tur bilan himoyalandi. a11y — oxirida qo'shiladigan "bezak" emas, har qadamdagi odat.

Ikki halol izoh:

  • Birinchi o'lchovda dialog color-contrast ×4 berdi. Sabab — o'lchov dialog ochilish animatsiyasi (0,15 s) paytida olingan edi: shaffoflik hali to'liq emas. Animatsiya tugashi kutilgach — 0. Avtomatik vosita ham noto'g'ri vaqtda o'lchasa, noto'g'ri natija beradi.
  • "Qo'lda ko'rish kerak" (incomplete) ro'yxati bo'sh emas: statistika paneli + menyu holatida axe color-contrast ×3 (fon rangini aniqlay olmadi — element ustma-ust) va aria-valid-attr-value ×1 ni "tekshira olmadim, odam qarasin" deb belgiladi. Bu buzilish emas — axe o'zi ishonchsiz bo'lgan joylarni sanaydi. Ular qo'lda ko'rildi.

6.2 Malika "ko'radigan" daraxt

axe qoidalarni tekshiradi, lekin "Malika nimani eshitadi?" degan savolga to'g'ridan-to'g'ri javob bermaydi. Buning uchun kanon e2e ssenariysi Chrome'dan to'liq a11y daraxtini oldi (DevTools protokoli, Accessibility.getFullAXTree) va beshta tekshiruv qildi. Haqiqiy natija (natija-18-40.txt):

text
✅ Malika: nomsiz boshqaruv elementi yo'q (button, checkbox, textbox…): []
✅ Malika: katak nomi — vazifa matni (label): ["Non olish","Sut olish","Kitob o'qish"]
✅ Malika: ro'yxat nomi «Ro'yxat» (aria-labelledby), 3 ta element: [true,3]
✅ Malika: jonli hududlar (role=status) — e'lon, aloqa, qidiruv, statistika holati, forma xatosi: true
✅ Malika: statistika tugmasi — kengaytirilgan (expanded), menyu tugmasi — haspopup: [true,"menu"]

Har qator — bugungi bir bo'lim:

  • Nomsiz element yo'q — «Har boshqaruv elementining nomi» bo'limi.
  • Katak nomi — vazifa matni. Har katak <label> ichida, demak Malika "Non olish, belgilash katagi, belgilanmagan" deb eshitadi, faqat "katak" emas.
  • Ro'yxat nomi — <ul aria-labelledby> sarlavhaga ulangan. Malika "Ro'yxat, ro'yxat, 3 ta element" deb eshitadi.
  • Jonli hududlar — beshta role="status": e'lon, aloqa holati, qidiruv natijasi, statistika holati, forma xatosi.
  • Holatlar — aria-expanded va aria-haspopup="menu".

Bu tekshiruvlar e2e'da qoladi: kelajakda kimdir nomsiz tugma qo'shsa — test yiqiladi. Bugun kod o'zgarmadi. Keyingi darsda Bobur ssenariysi va skip link qo'shiladi, commit — o'shanda.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Tab bilan elementga yetib bo'lmaydi <div onClick> <button>
NVDA: "tugma" (nomsiz) ikonka tugmada matn yo'q aria-label yoki sr-only matn
NVDA: "tahrirlanadigan matn" (nomsiz) placeholder bor, <label> yo'q <label htmlFor>
Invalid DOM property `for`. Did you mean `htmlFor`? JSX'da for htmlFor
ikki maydonga bitta yorliq qo'lda yozilgan bir xil id useId
xato faqat qizil rangda holat bog'lanmagan aria-invalid + aria-describedby
role="button" li element Enter'ga javob bermaydi ARIA xulq bermaydi native teg yoki klaviatura ishlovchisi
axe: color-contrast faqat ba'zan animatsiya paytida o'lchandi animatsiya tugashini kuting

8. Mashqlar

Mashqlar kurs/mashqlar/18/39-a11y/ da.

1-mashq (oson): nima eshitiladi?

Malika har elementda nomni eshitadi. Nomni yozing (nom yo'q bo'lsa — yo'q).

  • <button aria-label="Yopish">✕</button> —
  • <button><img src="savat.svg" alt="" /></button> —
  • <label>Telefon <input /></label> dagi maydon —
Yechim
  • Yopish — aria-label ichki "" dan ustun.
  • yo'q — alt="" rasmni bezak qildi, tugmada boshqa matn yo'q. Bu xato: tugma ichidagi yagona rasmda alt amalni aytishi kerak (alt="Savat").
  • Telefon — <label> maydonni o'rab olgan, htmlFor siz ham bog'langan. Lekin htmlFor + id ishonchliroq: ba'zi yordamchi texnologiyalar o'ralgan yorliqni yaxshi tanimaydi.

2-mashq (o'rta): div larni tuzating

Quyidagi taom kartasida uch a11y xatosi bor. Toping va tuzating: semantik teglar, nomli tugma, alt.

jsx
function DishCard({ onAdd, onFavorite }) {
  return (
    <div className="karta">
      <img src="osh.webp" />
      <div className="sarlavha">Osh</div>
      <div className="tugma" onClick={onAdd}>Savatga</div>
      <button onClick={onFavorite}>♥</button>
    </div>
  );
}
Yechim
jsx
function DishCard({ onAdd, onFavorite }) {
  return (
    <article className="karta">
      <img src="osh.webp" alt="Osh — sabzi, go'sht, guruch" />
      <h3>Osh</h3>
      <button onClick={onAdd}>Savatga</button>{" "}
      <button aria-label="Sevimlilarga qo'shish" onClick={onFavorite}>
        ♥
      </button>
    </article>
  );
}

export default function App() {
  return (
    <DishCard
      onAdd={() => console.log("savatga: Osh")}
      onFavorite={() => console.log("sevimli: Osh")}
    />
  );
}

Konsolda:

text
savatga: Osh
sevimli: Osh

Ekranda:

text
Osh
Savatga ♥

Uch tuzatish: (1) <img> ga alt — rasm ma'lumot beradi; (2) <div onClick> → <button> — Tab, Enter, rol; (3) "" tugmaga aria-label. Qo'shimcha: sarlavha <div> emas, <h3> (Malika H tugmasi bilan taomdan taomga sakraydi), karta <article> — mustaqil kontent bo'lagi. harakat dagi bos("Sevimlilarga qo'shish") tugmani aria-label bo'yicha topdi — testlar ham Malika kabi nom bo'yicha qidiradi.

3-mashq (qiyin): akkordeon

«Ichimliklar» bo'limi: sarlavhadagi tugma bosilsa, ro'yxat ochiladi yoki yopiladi. Talablar:

  1. Tugma — <h2> ichida (<h2 role="button"> emas).
  2. aria-expanded holatni bildiradi, aria-controls — ro'yxat id si (useId bilan).
  3. Yopiq ro'yxat — hidden atributi bilan (daraxtdan ham chiqadi).
  4. Bosilganda konsolga aria-expanded: va yangi qiymat (DOM'dan o'qilgan, render'dan keyin). Ishora: render'dan keyingi DOM — useEffect (useEffect I).
Yechim
jsx
import { useEffect, useId, useRef, useState } from "react";

export default function App() {
  const [open, setOpen] = useState(false);
  const listId = useId();
  const buttonRef = useRef(null);
  const first = useRef(true);
  useEffect(() => {
    if (first.current) {
      first.current = false;
      return;
    }
    const value = buttonRef.current.getAttribute("aria-expanded");
    console.log("aria-expanded:", value);
  }, [open]);
  return (
    <section>
      <h2>
        <button
          ref={buttonRef}
          aria-expanded={open}
          aria-controls={listId}
          onClick={() => setOpen(!open)}
        >
          Ichimliklar
        </button>
      </h2>
      <ul id={listId} hidden={!open}>
        <li>Ko'k choy — 5000 so'm</li>
        <li>Kompot — 7000 so'm</li>
      </ul>
    </section>
  );
}

Konsolda:

text
aria-expanded: true

Ekranda:

text
Ichimliklar
Ko'k choy — 5000 so'm
Kompot — 7000 so'm

Sarlavha <h2> bo'lib qoldi (Malika H bilan topadi), ichidagi <button> — bosiladigan. Atribut hidden yopiq ro'yxatni ko'rinishdan ham, a11y daraxtidan ham olib tashlaydi. first ref — birinchi render'dagi effektni o'tkazib yuborish uchun: bizga faqat bosishdan keyingi qiymat kerak. Bu — HTML'dagi details tegining qo'lda yozilgan varianti. Oddiy holatda details tegi o'zi yetarli (details va summary).

4-mashq: Vazifalar qadami

Loyihada feat/a11y-otish branch'ini oching. npm run build va npm run preview dan keyin uch tekshiruv qiling:

  1. Lighthouse → Accessibility.
  2. axe DevTools bilan asosiy ko'rinish va o'chirish dialogi.
  3. NVDA bilan: ro'yxat sarlavhasiga H bilan o'ting va birinchi vazifa katagi nomini eshiting.
Yechim
  • Lighthouse Accessibility — 100 (kanonda yakuniy o'lchov: 97–99 / 100 / 100 / 100, lighthouse-yakuniy.txt).
  • axe — 0 buzilish. Dialog uchun animatsiya tugashini kuting (aks holda color-contrast chiqishi mumkin).
  • NVDA: "Ro'yxat, sarlavha, 2-daraja", keyin ↓ — "Ro'yxat, 3 ta element", Tab — "Non olish, belgilash katagi, belgilanmagan". Aniq so'zlar NVDA versiyasi va tiliga qarab farq qiladi.
  • Kod o'zgarmadi, commit — keyingi darsda.

9. Real ishda

  • Qonun. Ko'p davlatlarda davlat va yirik kompaniya saytlari uchun a11y majburiy: AQShda ADA va Section 508, Yevropada European Accessibility Act (2025-yil iyundan). Xorijiy buyurtmachilar uchun ishlasangiz — WCAG 2.2 AA talab qilinadi.
  • Kod ko'rigi da a11y — alohida band: "nomi bormi?", "klaviatura bilan ishlaydimi?". Dizayn tizimlari (Radix, React Aria) a11y'ni komponent ichiga yashiradi — shuning uchun ular qadrli (Mantine, Chakra va React Aria).
  • Testlar — Testing Library'ning getByRole("button", { name: "Saqlash" }) so'rovi a11y daraxtidan qidiradi: nomsiz tugmani test ham topa olmaydi (RTL I).
  • Intervyu savollari: "div onClick nima uchun yomon?", "accessible name qanday hisoblanadi?", "aria-label va aria-labelledby farqi?", "ARIA'ning birinchi qoidasi?".

Xulosa

  • React a11y'ni o'zi bermaydi — semantik teg sizning tanlovingiz: amal — <button>, o'tish — <a href>.
  • Har boshqaruv elementining nomi bor: <label htmlFor> + useId, aria-label, alt; placeholder — yorliq emas.
  • aria-* JSX'da chiziqcha bilan; holat — aria-expanded, aria-invalid + aria-describedby; native element bo'lsa — ARIA emas.
  • Uch qavat: lint (jsx-a11y), axe (DevTools, Lighthouse, testlar), qo'lda — klaviatura va NVDA.
  • vazifalar: axe-core 4.13.0, olti holat — 0 buzilish; Malika daraxti — nomsiz element 0, beshta jonli hudud.

Keyingi dars: Fokus boshqaruvi va klaviatura navigatsiyasi — focus trap, fokusni qaytarish, roving tabindex, skip link va inert.

Manbalar

  • React: "Accessibility" (htmlFor, aria-*) — react.dev/reference/react-dom/components/common (2026-10 holatiga, React 19.3)
  • W3C: WAI-ARIA Authoring Practices Guide (APG) — w3.org/WAI/ARIA/apg
  • W3C: WCAG 2.2 — w3.org/TR/WCAG22
  • Deque: axe-core — github.com/dequelabs/axe-core
  • eslint-plugin-jsx-a11y — github.com/jsx-eslint/eslint-plugin-jsx-a11y
  • NV Access: NVDA — nvaccess.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React'da accessibility asoslari: semantik JSX, label, aria va avtomatik tekshiruv — IlmHamroh