Доступность drag and drop

React Aria предоставляет мощный набор хуков для создания интерфейсов с доступностью, включая функциональность drag and drop. Основной принцип заключается в том, чтобы разделить логику управления состоянием и поведение элементов от их визуального представления, что позволяет обеспечить поддержку клавиатуры, экранных читалок и других ассистивных технологий.

Управление состоянием drag and drop

Для управления состоянием перетаскивания используется хук useDragAndDrop, который инкапсулирует логику определения источников и целей перетаскивания. Он возвращает объект состояния с полями:

  • isDragging — булевый флаг, определяющий, выполняется ли текущая операция перетаскивания.
  • draggedItem — объект с данными перетаскиваемого элемента.
  • overItem — элемент, над которым находится курсор или фокус при перетаскивании.

Пример инициализации:

import {useDragAndDrop} from '@react-aria/dnd';
import {useState} from 'react';

function DndContainer({items}) {
  const [state, setState] = useState(items);
  const {dragProps, dropProps, isDragging} = useDragAndDrop({items, onMove: handleMove});

  function handleMove(fromIndex, toIndex) {
    const updated = [...state];
    const [moved] = updated.splice(fromIndex, 1);
    updated.splice(toIndex, 0, moved);
    setState(updated);
  }

  return (
    <div {...dropProps}>
      {state.map((item, index) => (
        <div key={item.id} {...dragProps(item)}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

Обеспечение поддержки клавиатуры

React Aria автоматически добавляет ARIA-атрибуты и события клавиатуры, но важно правильно структурировать DOM. Каждый элемент должен иметь:

  • tabIndex={0} для фокусируемости;
  • role="listitem" для обозначения элемента списка;
  • aria-grabbed для индикации, что элемент может быть перемещен;
  • aria-dropeffect на контейнере, чтобы обозначить возможность сброса.

Пример применения:

<div
  role="list"
  {...dropProps}
>
  {items.map(item => (
    <div
      role="listitem"
      tabIndex={0}
      aria-grabbed={isDragging && draggedItem.id === item.id}
      key={item.id}
      {...dragProps(item)}
    >
      {item.name}
    </div>
  ))}
</div>

С помощью этих атрибутов экранные читалки корректно сообщают пользователю о состоянии drag and drop, включая выбор и положение элементов.

Визуальные индикаторы состояния

Для повышения доступности важно предоставлять наглядные подсказки о состоянии перетаскивания:

  • Использование класса dragging для выделения перемещаемого элемента;
  • Подсветка drop target при наведении;
  • Появление маркера позиции, куда элемент будет перемещен.

React Aria позволяет связывать состояние и визуальное представление через хуки, без вмешательства в логику событий. Например:

<div
  className={`item ${isDragging && draggedItem.id === item.id ? 'dragging' : ''}`}
  {...dragProps(item)}
>
  {item.name}
</div>

Обработка событий drag и drop

React Aria поддерживает как нативные события HTML5, так и клавиатурные аналоги. Основные события:

  • onDragStart — инициализация перетаскивания, установка состояния draggedItem;
  • onDragOver — проверка возможности сброса;
  • onDrop — завершение операции и вызов onMove;
  • onDragEnd — очистка состояния и снятие визуальных индикаторов.

Для клавиатурного взаимодействия используются:

  • Стрелки вверх/вниз для перемещения фокуса между элементами;
  • Пробел или Enter для захвата/отпускания элемента;
  • Esc для отмены операции.

Совместимость с экранными читалками

React Aria генерирует ARIA-атрибуты, но правильная структура DOM критична:

  • Контейнер должен иметь role="list";
  • Каждый элемент — role="listitem", с атрибутом aria-grabbed;
  • Для доступной анимации изменения позиции элементов важно, чтобы DOM не перерисовывался полностью, а только обновлялись позиции элементов.

Практические советы

  1. Минимизировать сложные вложенные контейнеры, чтобы фокус оставался управляемым.
  2. Использовать useMove для сложных сценариев, где требуется анимация и сортировка элементов.
  3. Тестировать с клавиатурой и экранными читалками, проверяя чтение aria-grabbed и aria-dropeffect.
  4. Избегать навязчивых анимаций, которые мешают восприятию пользователями ассистивных технологий.

Интеграция с кастомными компонентами

React Aria позволяет интегрировать drag and drop с любыми визуальными библиотеками:

  • Передавать dragProps и dropProps в обертку компонента;
  • Использовать useRef для контроля DOM-элементов;
  • Поддерживать контролируемое состояние, чтобы внешние хранилища данных могли синхронизировать порядок элементов.

Пример интеграции с компонентом карточки:

function Card({item, dragProps}) {
  return (
    <div className="card" {...dragProps(item)}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </div>
  );
}

Использование хуков React Aria обеспечивает максимальную совместимость с ассистивными технологиями при сохранении гибкости интерфейса и возможности кастомизации визуального оформления.