Акордеон і таби: продуктивність та доступність на сайті

Акордеони та таби часто стають джерелом проблем із продуктивністю та доступністю: анімації викликають зсуви макета, а прихований контент втрачає позиції в пошуку. Ми розробляємо ці компоненти з урахуванням 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+ проєктів з акордеонами та табами — ми знаємо всі підводні камені. Якщо ви хочете отримати аналогічний результат — зв'яжіться з нами для оцінки вашого проєкту. Для отримання консультації та оцінки – пишіть нам. Ми оцінимо задачу та запропонуємо оптимальне рішення. В середньому економія на підтримці є значною, а окупність швидкою.

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

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060