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

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

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Акордеон і таби: продуктивність та доступність на сайті
Простий
~1 день

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

Часті запитання

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

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1422
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1288
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    984
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    1001

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