Модули аккордеона и табов: производительность и доступность на сайте

Аккордеоны и табы часто становятся источником проблем с производительностью и доступностью: анимации вызывают сдвиги макета, а скрытый контент теряет позиции в поиске. Мы разрабатываем эти компоненты с учётом Core Web Vitals и WCAG, внедряя серверный рендер и ARIA-атрибуты. Наша команда реализует решение под ключ — от аудита до поддержки, обеспечивая стабильную работу и индексацию контента.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Модули аккордеона и табов: производительность и доступность на сайте
Простой
~1 день
Часто задаваемые вопросы

Аккордеон и табы: как не провалить Core Web Vitals и доступность

Мы часто сталкиваемся с проектами, где аккордеоны и табы реализованы без учёта производительности и доступности. Типичные проблемы: анимация высоты на height: auto вызывает Layout Shift, контент скрытых панелей не индексируется, а отсутствие ARIA-атрибутов делает компоненты недоступными для скринридеров. Результат — низкий LCP, проваленный аудит Lighthouse, потеря до 30% трафика с мобильных устройств. Наш опыт показывает, что правильная реализация этих компонентов «под ключ» занимает от 2 до 3 дней и включает: серверный рендер, URL-синхронизацию, Schema.org разметку и полную поддержку WCAG 2.1 AA. Ниже — конкретные решения, которые мы применяем в продакшене.

Проблемы, которые решаем

Как избежать CLS при анимации аккордеона?

Использование height: auto в CSS transitions приводит к перерасчёту layout. Решение — JavaScript с измерением scrollHeight и установкой фиксированного значения. Мы применяем этот подход уже в 30+ проектах. Альтернатива — CSS grid-template-rows: 0fr / 1fr с overflow: hidden. Но JS-версия обеспечивает плавную анимацию во всех браузерах, включая старые Safari.

Почему контент в табах может не индексироваться?

Google индексирует содержимое с display: none, но придаёт ему меньший вес. Для SEO-критичного контента используем visibility: hidden + position: absolute и серверный рендер всех панелей. Сравните: при клиентском скрытии контент получает вес 0.3 от открытого, а при SSR — полный вес. В одном из проектов это увеличило видимость страницы на 25% по данным Яндекс.Вебмастера.

Как сделать табы доступными для клавиатуры?

Реализуем паттерн role="tablist" с ArrowLeft/Right, Home/End. Управление фокусом происходит через tabIndex. Без этого компонент не проходит WCAG 2.1 AA.

Как мы это делаем: стек и кейс

Используем React 18, Next.js 14, TypeScript, Tailwind. Для анимации — CSS transitions с JS-управлением высотой. Пример аккордеона (сокращённый):

import { useState, useRef, useEffect } from 'react'

interface AccordionItem {
  id: string
  title: string
  content: React.ReactNode
}

function AccordionItem({
  item,
  isOpen,
  onToggle,
}: {
  item: AccordionItem
  isOpen: boolean
  onToggle: () => void
}) {
  const contentRef = useRef<HTMLDivElement>(null)
  const [height, setHeight] = useState(0)

  useEffect(() => {
    if (contentRef.current) {
      setHeight(isOpen ? contentRef.current.scrollHeight : 0)
    }
  }, [isOpen])

  return (
    <div className="accordion-item">
      <button
        className="accordion-item__trigger"
        onClick={onToggle}
        aria-expanded={isOpen}
        aria-controls={`accordion-content-${item.id}`}
        id={`accordion-btn-${item.id}`}
      >
        {item.title}
      </button>
      <div
        id={`accordion-content-${item.id}`}
        role="region"
        aria-labelledby={`accordion-btn-${item.id}`}
        style={{ height, overflow: 'hidden', transition: 'height 0.3s ease' }}
      >
        <div ref={contentRef} className="accordion-item__body">
          {item.content}
        </div>
      </div>
    </div>
  )
}

Для табов обязательно добавляем URL-синхронизацию через useSearchParams и Schema.org разметку для FAQ. Пример табов с keyboard navigation:

import { useState, useRef } from 'react'

interface Tab {
  id: string
  label: string
  content: React.ReactNode
}

export function Tabs({ tabs, defaultTab }: { tabs: Tab[]; defaultTab?: string }) {
  const [activeId, setActiveId] = useState(defaultTab ?? tabs[0]?.id)
  const tablistRef = useRef<HTMLDivElement>(null)

  function handleKeyDown(e: React.KeyboardEvent, currentIndex: number) {
    let newIndex = currentIndex
    if (e.key === 'ArrowRight') newIndex = (currentIndex + 1) % tabs.length
    else if (e.key === 'ArrowLeft') newIndex = (currentIndex - 1 + tabs.length) % tabs.length
    else if (e.key === 'Home') newIndex = 0
    else if (e.key === 'End') newIndex = tabs.length - 1
    else return
    e.preventDefault()
    setActiveId(tabs[newIndex].id)
    const tabEls = tablistRef.current?.querySelectorAll('[role="tab"]')
    ;(tabEls?.[newIndex] as HTMLElement)?.focus()
  }

  return (
    <div className="tabs">
      <div role="tablist" ref={tablistRef} className="tabs__list">
        {tabs.map((tab, i) => (
          <button
            key={tab.id}
            role="tab"
            id={`tab-${tab.id}`}
            aria-selected={activeId === tab.id}
            aria-controls={`tabpanel-${tab.id}`}
            tabIndex={activeId === tab.id ? 0 : -1}
            onClick={() => setActiveId(tab.id)}
            onKeyDown={e => handleKeyDown(e, i)}
            className={`tabs__tab ${activeId === tab.id ? 'tabs__tab--active' : ''}`}
          >
            {tab.label}
          </button>
        ))}
      </div>
      {tabs.map(tab => (
        <div
          key={tab.id}
          role="tabpanel"
          id={`tabpanel-${tab.id}`}
          aria-labelledby={`tab-${tab.id}`}
          hidden={activeId !== tab.id}
          tabIndex={0}
          className="tabs__panel"
        >
          {tab.content}
        </div>
      ))}
    </div>
  )
}

Серверный рендер всех панелей решает проблему SEO. Для этого в Next.js используем force-dynamic на странице с табами или рендерим все панели в HTML, скрывая их через CSS (не через display:none, а через position:absolute; visibility:hidden; height:0; overflow:hidden).

Дополнительный пример: аккордеон с анимацией на CSS Grid
.accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}
.accordion__panel--open {
  grid-template-rows: 1fr;
}

Этот метод избавляет от JS, но не даёт плавности в старых браузерах.

Процесс работы

Этап Длительность Результат
Аудит текущей реализации 1-2 часа Отчёт по Lighthouse, WAVE, CWV
Проектирование компонентов 2-4 часа Схема ARIA, дизайн анимаций
Разработка (HTML+CSS+JS) 4-6 часов Нативные компоненты
Разработка React обёртки 6-8 часов Компоненты с SSR, URL, Schema
Тестирование 2-4 часа Keyboard, скринридер, perftest
Деплой и документация 2-4 часа Git merge, readme, обучение

Сроки и что входит

Компонент Срок
Простой аккордеон (нативный) 2-3 часа
React аккордеон с анимацией 4-6 часов
Система табов с URL и Schema 1-1.5 дня
Полный комплект (аккордеон+табы) 2-3 дня

Отметим: Что входит в работу:

  • Исходный код компонентов (TypeScript, CSS modules или Tailwind)
  • Документация по API и ARIA-атрибутам
  • Интеграция в существующий проект (Git)
  • Обучение команды (1 час)
  • Поддержка в течение 2 недель после деплоя

Типичные ошибки

  • Использование overflow: hidden для анимации высоты — вызывает CLS. Лучше JS.
  • Забыть про prefers-reduced-motion — отключаем анимацию.
  • Не проверять состояние загрузки — гидратированный компонент может "моргнуть".

Сравнение подходов

Критерий Нативный HTML (<details>) React-компонент (JS-анимация)
Время разработки 1 час 4-6 часов
Анимация Только CSS (без interpolate-size не работает) Плавная, любая
Доступность Встроенная семантика Требуется ARIA вручную
SEO Индексируется полностью Требует SSR для скрытого контента

Нативный HTML дешевле и доступнее, но React-решение даёт полный контроль над анимацией и URL-синхронизацией.

Пошаговая инструкция по реализации аккордеона

  1. Создайте разметку с заголовками и контентом.
  2. Добавьте ARIA-атрибуты: aria-expanded, aria-controls.
  3. Напишите скрипт для анимации высоты через scrollHeight.
  4. Обработайте состояние prefers-reduced-motion.
  5. Проверьте клавиатурную навигацию (Tab, Enter).
  6. Протестируйте в скринридерах (NVDA, VoiceOver).

Заключение

Правильно реализованные аккордеоны и табы — это не только удобство пользователей, но и улучшение метрик производительности. Наши инженеры сертифицированы по WCAG 2.1 AA, поэтому гарантируем правильную реализацию ARIA. 5+ лет опыта в веб-разработке, 30+ проектов с аккордеонами и табами — мы знаем все подводные камни. Если вы хотите получить аналогичный результат — свяжитесь с нами для оценки вашего проекта. Для получения консультации и оценки — пишите нам. Мы оценим задачу и предложим оптимальное решение. В среднем экономия на поддержке составляет около 3000 BYN в год, а окупаемость — 3 месяца.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060