← Назад к вопросам

Как достигается доступность?

2.0 Middle🔥 201 комментариев
#HTML и CSS

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Доступность (Accessibility) в веб-разработке

Доступность (A11y, от letters a и y с 11 буквами между ними) — это практика создания веб-контента, доступного для всех людей, включая людей с инвалидностью. Это правовое, этическое и экономическое требование.

Кто нуждается в доступности?

  • Люди с нарушением зрения (слепые, слабовидящие) — используют скрин-ридеры
  • Люди с нарушением слуха — нужны субтитры и визуальные индикаторы
  • Люди с нарушением двигательных функций — навигация только клавиатурой
  • Люди с когнитивными нарушениями — простая и понятная структура
  • Люди с заболеваниями глаз — контрастность, размер текста
  • Пожилые люди — просто полезно для всех

1. Семантичный HTML

Основная основа доступности — использование правильных HTML элементов.

Плохо: таблица вёрстана на divs

<!-- Неправильно -->
<div class="button" onclick="submitForm()">Submit</div>
<div>Page Title</div>
<div>Content goes here</div>

<!-- Правильно -->
<button type="submit">Submit</button>
<h1>Page Title</h1>
<main>
  <article>
    Content goes here
  </article>
</main>

Скрин-ридер понимает семантику:

<!-- Скрин-ридер скажет "heading level 1: Page Title" -->
<h1>Page Title</h1>

<!-- Скрин-ридер скажет "button: Submit" -->
<button type="submit">Submit</button>

<!-- Скрин-ридер скажет "link: Click here" -->
<a href="/page">Click here</a>

Правильная иерархия заголовков

<!-- Правильно -->
<h1>Main Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h2>Section 2</h2>

<!-- Неправильно: пропускаем уровни -->
<h1>Main Title</h1>
<h3>Subsection (нет h2!)</h3>

2. ARIA атрибуты (Accessible Rich Internet Applications)

ARIA помогает когда семантического HTML недостаточно.

aria-label: описание для элемента

<!-- Кнопка без видимого текста -->
<button aria-label="Close dialog"></button>

<!-- Скрин-ридер скажет: "button: Close dialog" -->

aria-labelledby: связь с другим элементом

<h2 id="dialog-title">Confirm Action</h2>
<p id="dialog-desc">Are you sure?</p>
<div role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
  <!-- диалог -->
</div>

aria-hidden: скрыть от скрин-ридера

<!-- Декоративный элемент, не нужно читать -->
<span aria-hidden="true"></span>

<!-- Но если это важно, не используй aria-hidden -->
<span aria-hidden="true"></span> <!-- Плохо, нужно читать -->
<button aria-label="Close"></button> <!-- Хорошо -->

aria-live: уведомления об изменениях

<!-- Скрин-ридер объявит изменение в реальном времени -->
<div aria-live="polite" aria-atomic="true">
  Items in cart: 5
</div>

<!-- Приоритет: polite (дождаться паузы) vs assertive (немедленно) -->

aria-expanded: состояние (открыто/закрыто)

<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
<nav id="menu" hidden>
  <ul>
    <li><a href="/home">Home</a></li>
  </ul>
</nav>

<!-- При клике обновляем aria-expanded -->
button.setAttribute("aria-expanded", "true");
menu.hidden = false;

3. Клавиатурная навигация

Многие люди не используют мышь.

Focusable элементы

<!-- Интерактивные элементы должны быть фокусируемые -->
<button>Submit</button>      <!-- Фокусируем по умолчанию -->
<a href="/">Link</a>          <!-- Фокусируем по умолчанию -->
<input type="text" />         <!-- Фокусируем по умолчанию -->

<!-- Если не интерактивно, но нужно быть фокусируемым -->
<div tabindex="0">Focusable div</div>  <!-- tabindex=0: в tab order -->
<div tabindex="-1">Not in tab order, but can focus programmatically</div>

Порядок табуляции

<!-- Плохо: нелогичный порядок -->
<input type="text" tabindex="3" />
<input type="text" tabindex="1" />
<input type="text" tabindex="2" />

<!-- Хорошо: используй семантический порядок в HTML -->
<input type="text" />  <!-- tabindex не нужен, порядок из HTML -->
<input type="text" />
<input type="text" />

Видимый focus

/* Никогда не удаляй focus outline без замены! */

/* Плохо -->
button:focus { outline: none; }

/* Хорошо -->
button:focus { outline: 3px solid blue; }

/* Или стильный focus -->
button:focus-visible {
  outline: 2px solid #4CAF50;
  outline-offset: 2px;
}

4. Контрастность цветов

Должна быть достаточная контрастность для людей с нарушением зрения.

WCAG стандарты контрастности

  • AA уровень: минимум 4.5:1 для текста, 3:1 для крупного текста
  • AAA уровень: минимум 7:1 для текста, 4.5:1 для крупного текста
/* Хорошая контрастность -->
.text {
  color: #000000;              /* чёрный -->
  background-color: #ffffff;   /* белый -->
  /* Контраст: 21:1 - идеально -->
}

/* Плохая контрастность -->
.text {
  color: #777777;              /* серый -->
  background-color: #888888;   /* тёмно-серый -->
  /* Контраст: ~1.1:1 - очень плохо -->
}

Проверить контрастность: WebAIM Contrast Checker

5. Текстовые альтернативы

alt текст для изображений

<!-- Плохо: пустой alt или неинформативный -->
<img src="photo.jpg" alt="" />
<img src="photo.jpg" alt="image" />

<!-- Хорошо: описывает содержимое -->
<img src="team-photo.jpg" alt="Team of 5 engineers standing in office" />

<!-- Для декоративных изображений -->
<img src="decoration.jpg" alt="" />  <!-- пустой alt -->
<img src="decoration.jpg" aria-hidden="true" />  <!-- или aria-hidden -->

Субтитры и расшифровки

<!-- Видео с субтитрами -->
<video width="400" height="300" controls>
  <source src="video.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" />
</video>

<!-- Аудио с расшифровкой -->
<audio controls>
  <source src="podcast.mp3" type="audio/mpeg" />
</audio>
<p><a href="transcript.txt">Read transcript</a></p>

6. React компоненты с доступностью

// components/Modal.tsx
interface ModalProps {
  isOpen: boolean;
  title: string;
  onClose: () => void;
  children: React.ReactNode;
}

export function Modal({ isOpen, title, onClose, children }: ModalProps) {
  if (!isOpen) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      onClick={(e) => e.currentTarget === e.target && onClose()}
    >
      <div className="modal-content">
        <button
          aria-label="Close dialog"
          onClick={onClose}
          className="close-btn"
        ></button>
        <h2 id="modal-title">{title}</h2>
        {children}
      </div>
    </div>
  );
}

// components/Dropdown.tsx
export function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const buttonRef = useRef<HTMLButtonElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape" && isOpen) {
        setIsOpen(false);
        buttonRef.current?.focus();
      }
    };

    if (isOpen) {
      document.addEventListener("keydown", handleKeyDown);
    }

    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [isOpen]);

  return (
    <div>
      <button
        ref={buttonRef}
        aria-expanded={isOpen}
        aria-controls="dropdown-menu"
        onClick={() => setIsOpen(!isOpen)}
      >
        Options
      </button>
      {isOpen && (
        <nav id="dropdown-menu" ref={menuRef}>
          <ul>
            <li><a href="#">Option 1</a></li>
            <li><a href="#">Option 2</a></li>
          </ul>
        </nav>
      )}
    </div>
  );
}

7. Тестирование доступности

Инструменты

  • axe DevTools — браузерное расширение для проверки WCAG
  • WAVE — WebAIM инструмент для визуализации проблем
  • Lighthouse — встроено в Chrome DevTools
  • jest-axe — для unit тестов доступности
// Пример теста доступности
import { render } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";

export {}; // Для TypeScript
expect.extend(toHaveNoViolations);

it("should have no accessibility violations", async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

Ручное тестирование

1. Отключи мышь, навигируй только клавиатурой (Tab, Enter, Arrows)
2. Используй скрин-ридер (NVDA, JAWS, VoiceOver на Mac)
3. Увеличь текст до 200%
4. Проверь контрастность
5. Отключи JS и проверь базовую функциональность

8. Практический чеклист

  • Семантичный HTML (h1-h6, nav, main, article, button, link)
  • alt текст на всех значащих изображениях
  • Все интерактивные элементы фокусируемы (Tab работает)
  • Focus видимый (outline или другой стиль)
  • Контрастность минимум 4.5:1 для текста
  • ARIA labels где нужно (button без текста, иконки)
  • Форма с label для каждого input
  • Ошибки форм понятны и легко исправляются
  • Видео с субтитрами
  • Никаких миганий > 3 раз в секунду
  • Тесты с jest-axe или axe DevTools

Вывод

Доступность — это не опция, это requirement. Хороший фронтендер учитывает a11y с начала проекта, не в конце. Это не сложно, если следовать стандартам WCAG и использовать семантичный HTML. В итоге все выигрывают: люди с инвалидностью получают доступ, юзеры с хорошим зрением получают лучший контент структуру, SEO улучшается.