Tabs и аккордеоны

Компоненты вкладок (Tabs) позволяют структурировать интерфейс, разделяя содержимое на логические секции, доступные без перезагрузки страницы. В экосистеме SvelteKit UI-библиотек (например, Skeleton, Flowbite-Svelte, Radix Svelte) вкладки реализуются с учетом реактивности Svelte и принципа декларативного UI.

Базовая структура

Типичная реализация вкладок включает три ключевых элемента:

  • контейнер вкладок
  • список триггеров (заголовков вкладок)
  • панели содержимого

Пример на Svelte:

<script>
  let activeTab = 'tab1';
</script>

<div class="tabs">
  <button on:click={() => activeTab = 'tab1'} class:active={activeTab === 'tab1'}>
    Вкладка 1
  </button>
  <button on:click={() => activeTab = 'tab2'} class:active={activeTab === 'tab2'}>
    Вкладка 2
  </button>
</div>

<div class="tab-content">
  {#if activeTab === 'tab1'}
    <p>Контент первой вкладки</p>
  {:else if activeTab === 'tab2'}
    <p>Контент второй вкладки</p>
  {/if}
</div>

Реактивность и состояние

Ключевая особенность — использование реактивных переменных. Переменная activeTab управляет отображением. При изменении значения происходит автоматический ререндер.

В более сложных случаях состояние можно:

  • хранить в store (например, writable)
  • синхронизировать с URL (через query params)
  • управлять извне (controlled component)

Доступность (Accessibility)

Корректная реализация вкладок требует соблюдения стандартов ARIA:

  • role="tablist" — контейнер вкладок
  • role="tab" — кнопки
  • role="tabpanel" — панели
  • aria-selected — активная вкладка
  • aria-controls — связь вкладки и панели

Пример:

<div role="tablist">
  <button role="tab" aria-selected={activeTab === 'tab1'}>
    Tab 1
  </button>
</div>

Это особенно важно при использовании готовых UI-библиотек, где доступность уже встроена.

Использование в UI-библиотеках

Skeleton (SvelteKit UI)

<TabGroup>
  <TabList>
    <Tab>Profile</Tab>
    <Tab>Settings</Tab>
  </TabList>

  <TabPanels>
    <TabPanel>Profile content</TabPanel>
    <TabPanel>Settings content</TabPanel>
  </TabPanels>
</TabGroup>

Особенности:

  • автоматическое управление состоянием
  • поддержка keyboard navigation
  • стилизация через Tailwind

Flowbite-Svelte

<Tabs>
  <Tab title="Tab 1">Content 1</Tab>
  <Tab title="Tab 2">Content 2</Tab>
</Tabs>

Поддерживает:

  • lazy rendering
  • иконки
  • вертикальные вкладки

Расширенные паттерны

Ленивый рендеринг (Lazy Rendering)

Содержимое вкладки создается только при активации:

{#if activeTab === 'tab1'}
  <HeavyComponent />
{/if}

Это уменьшает нагрузку при инициализации.

Динамические вкладки

{#each tabs as tab}
  <button on:click={() => activeTab = tab.id}>
    {tab.title}
  </button>
{/each}

Подходит для CMS-интерфейсов и дашбордов.

Вкладки с маршрутизацией

Интеграция с SvelteKit:

import { goto } from '$app/navigation';

goto(`/settings?tab=profile`);

Позволяет сохранять состояние при перезагрузке страницы.


Аккордеоны: компактное раскрытие информации

Аккордеоны используются для отображения скрытого контента, который раскрывается по клику. Это особенно полезно для FAQ, списков настроек и вложенных структур.

Базовая реализация

<script>
  let open = false;
</script>

<div class="accordion">
  <button on:click={() => open = !open}>
    Заголовок
  </button>

  {#if open}
    <div class="content">
      Скрытый контент
    </div>
  {/if}
</div>

Управление несколькими секциями

<script>
  let openIndex = null;

  function toggle(index) {
    openIndex = openIndex === index ? null : index;
  }
</script>

{#each items as item, i}
  <div>
    <button on:click={() => toggle(i)}>
      {item.title}
    </button>

    {#if openIndex === i}
      <div>{item.content}</div>
    {/if}
  </div>
{/each}

Множественное раскрытие

let openItems = new Set();

function toggle(id) {
  if (openItems.has(id)) {
    openItems.delete(id);
  } else {
    openItems.add(id);
  }
}

Позволяет открывать несколько секций одновременно.

Анимации

Svelte предоставляет встроенные инструменты:

import { slide } from 'svelte/transition';

{#if open}
  <div transition:slide>
    Контент
  </div>
{/if}

Дополнительно:

  • fade
  • scale
  • кастомные transition

Доступность

Рекомендуется использовать:

  • aria-expanded
  • aria-controls
  • id для связи элементов
<button aria-expanded={open}>

Использование в UI-библиотеках

Skeleton

<Accordion>
  <AccordionItem>
    <AccordionHeader>Title</AccordionHeader>
    <AccordionContent>Content</AccordionContent>
  </AccordionItem>
</Accordion>

Особенности:

  • автоматическая анимация
  • управление состоянием
  • стилизация через классы

Flowbite-Svelte

<Accordion>
  <AccordionItem title="Section 1">
    Content 1
  </AccordionItem>
</Accordion>

Поддерживает:

  • иконки
  • множественные открытые элементы
  • кастомные стили

Сравнение Tabs и Accordion

Характеристика Tabs Accordion
Отображение Одна секция Одна или несколько
Пространство Горизонтальное Вертикальное
Использование Переключение контекста Раскрытие деталей
UX Быстрый доступ Последовательное чтение
Подходит для Панелей, настроек FAQ, списков

Архитектурные подходы

Controlled vs Uncontrolled

  • Uncontrolled — библиотека сама управляет состоянием
  • Controlled — состояние передается извне
<Tabs bind:value={activeTab} />

Композиция компонентов

Современные UI-библиотеки используют composition API:

<Tabs>
  <Tabs.List>
    <Tabs.Trigger />
  </Tabs.List>
  <Tabs.Content />
</Tabs>

Это повышает гибкость и переиспользуемость.


Интеграция с SvelteKit

SSR и гидратация

Tabs и Accordion должны корректно работать при серверном рендеринге:

  • избегать прямого доступа к DOM
  • использовать onMount при необходимости

URL-состояние

export function load({ url }) {
  return {
    tab: url.searchParams.get('tab') ?? 'default'
  };
}

Позволяет:

  • делиться ссылками
  • восстанавливать состояние

Практические рекомендации

  • Tabs подходят для равнозначных разделов
  • Accordion — для иерархических данных
  • избегать вложенных аккордеонов без необходимости
  • использовать анимации умеренно
  • соблюдать accessibility-стандарты
  • применять lazy rendering для тяжелых компонентов
  • синхронизировать состояние с URL при сложной навигации

Расширение и кастомизация

Темизация

Большинство библиотек используют Tailwind:

class="bg-primary text-white"

Слоты

<AccordionItem>
  <slot name="header" />
  <slot />
</AccordionItem>

Позволяет внедрять кастомный контент.

Переиспользование логики

Вынесение логики в composable:

export function useAccordion() {
  let open = writable(null);
  return { open };
}

Производительность

  • минимизация ререндеров
  • использование keyed each
  • lazy loading компонентов
  • избегание глубоких вложенностей

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

  • отсутствие aria-атрибутов
  • хранение состояния в DOM вместо реактивных переменных
  • избыточные анимации
  • несогласованность состояния и UI
  • отсутствие контроля за фокусом при навигации с клавиатуры