Безстилевые компоненты

Безстилевые компоненты представляют собой элементы интерфейса, которые предоставляют функциональность без привязки к конкретной визуальной стилистике. В SvelteKit это особенно полезно для построения гибких UI-библиотек, где дизайн задаётся внешними CSS-фреймворками или Tailwind, а компоненты остаются полностью универсальными.

Важные преимущества безстилевых компонентов:

  • Полная кастомизация внешнего вида – стили можно задавать через классы, CSS-переменные или сторонние CSS-библиотеки.
  • Повторное использование – один компонент может быть использован в разных проектах с различными темами.
  • Изоляция логики – компоненты сосредоточены на поведении, а не на визуальной подаче.

Структура безстилевого компонента

Типичный безстилевой компонент в SvelteKit состоит из трёх частей:

  1. Логикаscript блок, где определяются переменные, события и функции.
  2. Разметкаmarkup, описывающая структуру HTML.
  3. Слоты и пропсы – для передачи данных и вложенного контента.

Пример простого безстилевого компонента кнопки:

<script lang="ts">
  export let type: 'button' | 'submit' | 'reset' = 'button';
  export let disabled: boolean = false;
  export let onClick: () => void;
</script>

<button type={type} disabled={disabled} on:click={onClick}>
  <slot>Кнопка</slot>
</button>

Ключевые моменты:

  • Компонент не содержит встроенного CSS.
  • Внешний вид определяется через классы, переданные при использовании.
  • Слот позволяет полностью заменять содержимое кнопки без изменения логики.

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

Безстилевые компоненты часто используют prop class для передачи кастомных стилей.

Пример использования кнопки:

<MyButton class="bg-blue-500 text-white px-4 py-2 rounded" onCl ick={handleClick}>
  Отправить
</MyButton>

Такой подход позволяет полностью отделить визуальную часть от логики, сохраняя компонент универсальным.

Дополнительно можно использовать композицию классов через utility-библиотеки (например, clsx или classnames):

<script lang="ts">
  import clsx from 'clsx';
  export let primary: boolean = false;
  export let className: string = '';
</script>

<button class={clsx('px-4 py-2 rounded', primary && 'bg-blue-500 text-white', className)}>
  <slot></slot>
</button>

Слоты и вложенные компоненты

Слоты — основной инструмент безстилевых компонентов для поддержки вложенного контента.

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

<div class="border rounded p-4 shadow">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Использование:

<Card>
  <h2 slot="header">Заголовок</h2>
  <p>Основной контент карточки.</p>
  <button slot="footer" class="btn-primary">Действие</button>
</Card>

Слоты позволяют:

  • Определять разные части интерфейса без жесткого связывания с CSS.
  • Делать компонент максимально гибким и расширяемым.

Управление состоянием без стилей

Безстилевые компоненты часто работают с внутренним состоянием, но не навязывают его визуализацию. Например, компонент раскрывающегося меню:

<script lang="ts">
  import { writable } from 'svelte/store';
  const open = writable(false);

  function toggle() {
    open.update(value => !value);
  }
</script>

<div>
  <button on:click={toggle}>
    <slot name="trigger">Открыть меню</slot>
  </button>
  {#if $open}
    <div>
      <slot name="content">Содержимое меню</slot>
    </div>
  {/if}
</div>

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

  • Логика открытия/закрытия отделена от визуального оформления.
  • Внешние стили и анимации могут быть добавлены на уровне родительского компонента или через CSS-классы.

Поддержка accessibility (a11y)

Безстилевые компоненты требуют особого внимания к доступности. Основные рекомендации:

  • ARIA-атрибуты для интерактивных элементов (role, aria-expanded, aria-controls).
  • Фокусировка клавиатурой через tabindex и обработчики on:keydown.
  • Семантическая разметка вместо декоративных элементов.

Пример accessible переключателя:

<script lang="ts">
  export let checked: boolean = false;
  export let onChange: (value: boolean) => void;
</script>

<div role="switch" aria-checked={checked} tabindex="0"
     on:click={() => onChange(!checked)}
     on:keydown={(e) => e.key === 'Enter' && onChange(!checked)}>
  <slot>{checked ? 'Включено' : 'Выключено'}</slot>
</div>

Интеграция с CSS-фреймворками и Tailwind

Безстилевые компоненты идеально сочетаются с утилитарными CSS-фреймворками.

Пример: кнопка с Tailwind и вариациями:

<script lang="ts">
  export let variant: 'primary' | 'secondary' = 'primary';
  export let className: string = '';
</script>

<button class={`px-4 py-2 rounded ${variant === 'primary' ? 'bg-blue-500 text-white' : 'bg-gray-200 text-black'} ${className}`}>
  <slot></slot>
</button>

Это обеспечивает:

  • Консистентность визуального дизайна.
  • Возможность расширения через дополнительные классы без модификации компонента.

Итоги по архитектуре

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

  • Отделение логики от стилей.
  • Использование слотов для вложенного контента.
  • Передача классов и стилей через prop class или utility-функции.
  • Поддержка доступности и семантики.
  • Гибкая интеграция с любыми CSS-фреймворками и темами.

Такая архитектура обеспечивает максимальную переиспользуемость, гибкость и чистоту кода, что критично для крупных SvelteKit UI-библиотек.