Svelte

Стилизация: scoped-стили и CSS-переменные

Команда добавляет компонент уведомления с классом .title и стилем для него. Через неделю в шапке сайта ломается заголовок: оказалось, у него тоже класс .title, и стиль из уведомления протёк наружу через глобальную таблицу стилей. В Svelte такого не происходит: стили внутри компонента по умолчанию видны только этому компоненту - компилятор добавляет каждому селектору уникальный хеш-атрибут. Чтобы тема при этом оставалась единой, оформление выносят в CSS-переменные, а не дублируют значения по компонентам.

  • Svelte изолирует стили компонента из коробки, без CSS-модулей или CSS-in-JS - это часть компилятора, а не отдельная библиотека
  • CSS-переменные (custom properties) - нативный механизм браузера для тем: смена темы это переопределение значений на :root без пересборки
  • Дизайн-системы (токены цвета, отступов, типографики) кладутся в CSS-переменные и переопределяются на уровне компонента или темы
  • Tailwind популярен в SvelteKit-проектах: утилитарные классы вместо ручного CSS, интеграция через плагин Vite и директивы в общем стиле
  • Тёмная тема в современных приложениях обычно сводится к одному набору CSS-переменных, переключаемому атрибутом data-theme или media-запросом

Предварительные знания

  • Компоненты Svelte: блок script, разметка и блок style внутри одного файла
  • Базовый CSS: селекторы по классу, свойства, каскад и специфичность на уровне идеи
  • Понимание, что один и тот же класс в глобальном CSS затрагивает все элементы с этим классом

Изоляция стилей на уровне компонента

В обычном HTML таблица стилей глобальна: правило .title затрагивает каждый элемент с таким классом на странице. В крупном проекте это рождает конфликты имён и страх трогать чужой CSS. Svelte решает это на этапе компиляции. Стили внутри блока style компонента применяются только к разметке этого компонента, и других компонентов они не касаются.

Под капотом компилятор генерирует уникальный класс-хеш (например svelte-x7y2k) и добавляет его и в разметку, и в селектор. Правило превращается в .title.svelte-x7y2k, поэтому совпадает только с h2 этого компонента. Другой компонент со своим .title получит другой хеш, и пересечения не будет. Никаких CSS-модулей или соглашений об именах не требуется.

Компилятор Svelte также предупреждает о неиспользуемых селекторах. Если в блоке style есть правило для .subtitle, а в разметке такого класса нет, при сборке появится предупреждение об unused CSS selector. Это помогает вычищать мёртвые стили, которые в глобальном CSS обычно копятся годами.

  • Глобальный CSS — Одно пространство имён на всё приложение. Класс .title влияет на любой элемент с этим классом. Конфликты решаются дисциплиной именования (BEM и подобные).
  • Scoped-стили Svelte — Каждый компонент изолирован хешем. Один и тот же класс в разных компонентах независим. Конфликтов имён нет по построению.

Как Svelte добивается того, что стили одного компонента не протекают в другой?

Выход за границу через :global()

Изоляция по умолчанию иногда мешает. Два случая встречаются чаще всего. Первый: разметка пришла извне - из слота, из svelte:html или сгенерирована из Markdown, и хеш-атрибут на неё не попал. Второй: нужны глобальные базовые правила (сброс отступов, стиль body). Для этого есть модификатор :global(), который выключает изоляцию для конкретного селектора.

Конструкция .prose :global(a) читается так: контейнер .prose остаётся изолированным к этому компоненту, но ссылки внутри него стилизуются глобально, независимо от того, откуда пришла разметка. Это типичный приём для контента из Markdown: сам компонент свой, а вложенные теги генерируются и хеша не имеют.

С :global() важна мера. Каждое глобальное правило возвращает риск конфликтов, ради устранения которых и существует изоляция. Глобальными стоит делать только то, что обязано быть глобальным: сбросы, базовая типографика, стилизация неконтролируемой разметки. Для всего остального изоляция по умолчанию безопаснее.

ЗадачаПодходПочему
Стиль элементов своего компонентаОбычный scoped-селекторИзоляция бесплатна и безопасна
Стиль содержимого слота / Markdown:global() для вложенных теговНа внешнюю разметку хеш не попадает
Сброс и базовая типографика:global(body), :global(*)Должно действовать на всю страницу
Тема и токены оформленияCSS-переменные на :rootНаследуются вниз без глобальных правил на каждый элемент

Компонент рендерит содержимое слота, где встречаются ссылки a, и их нужно стилизовать. Обычный scoped-селектор a на них не действует. Почему и как поправить?

CSS-переменные для тем и оформления

Изоляция отвечает на вопрос 'как не дать стилям протечь', но не на вопрос 'как держать единое оформление'. Если цвет акцента зашит числом в каждом компоненте, смена бренд-цвета превращается в поиск по всему коду. CSS-переменные решают это нативно: значение объявляется один раз, обычно на :root, и наследуется вниз по дереву. Компонент читает переменную через var() и не знает её конкретного значения.

Переключение темы сводится к смене атрибута data-theme на корневом элементе: переменные переопределяются, и все компоненты, читающие их через var(), обновляются мгновенно, без пересборки и без JavaScript-логики в каждом из них. Это работает потому, что CSS-переменные вычисляются браузером в рантайме и каскадируются как обычные свойства.

Прокидывание переменной в компонент через --имя=значение делает оформление частью публичного API: родитель настраивает экземпляр, не зная внутреннего CSS. Второй аргумент var() это значение по умолчанию: var(--badge-color, #6b7280) подставит серый, если переменную не передали.

Что происходит со всеми компонентами при смене data-theme на корневом элементе, если цвета заданы через CSS-переменные?

Интеграция Tailwind

Tailwind предлагает другой подход: вместо написания CSS разработчик собирает оформление из утилитарных классов прямо в разметке (flex, p-4, text-lg). В SvelteKit Tailwind подключается через плагин Vite, после чего утилиты доступны в любом компоненте. Это не конкурент scoped-стилям, а альтернативный стиль работы: для раскладки и типовых отступов многие предпочитают утилиты, оставляя блок style для нетривиальных случаев.

Tailwind и CSS-переменные хорошо уживаются. Современный Tailwind сам строит свою тему на CSS-переменных, поэтому токены дизайн-системы естественно ложатся в общий механизм. Типичное разделение: утилитарные классы берут на себя раскладку и расстояния, а семантические токены (бренд-цвет, цвет текста, переключение тёмной темы) остаются CSS-переменными, читаемыми и из утилит, и из scoped-стилей.

Выбор между Tailwind и обычными scoped-стилями - вопрос предпочтений команды, а не правильности. Scoped-стили дают изоляцию и предупреждения о неиспользуемых селекторах из коробки. Tailwind ускоряет типовую вёрстку и держит оформление рядом с разметкой. Их можно сочетать: утилиты для раскладки, блок style для сложной анимации или нестандартного селектора.

Как разумнее всего сочетать Tailwind и scoped-стили в Svelte-компоненте?

Связь с другими темами

Стилизация смыкается с доступностью и экосистемой. Связи:

  • Доступность — Контраст и видимый индикатор фокуса задаются стилями; CSS-переменные темы должны сохранять доступный контраст
  • Экосистема Svelte — Готовые библиотеки компонентов темизируются через CSS-переменные и часто рассчитаны на Tailwind

Итог

  • Стили в блоке style компонента по умолчанию изолированы: компилятор вешает на селекторы уникальный хеш-атрибут, и они не протекают наружу
  • Чтобы стиль вышел за границу компонента (стороннее содержимое, глобальные базовые правила), применяют модификатор :global()
  • CSS-переменные - нативный способ тем и оформления: одно значение на :root, переопределяемое глубже, без пересборки и дублирования
  • Переменную можно прокинуть в компонент инлайн (style props), что делает оформление частью публичного API компонента
  • Tailwind интегрируется в SvelteKit через плагин Vite и хорошо сочетается с CSS-переменными: утилиты для раскладки, переменные для токенов темы

Связанные уроки

  • sv-39-accessibility-a11y-warnings — Видимый фокус и контраст - часть доступности, и оба настраиваются стилями компонента и CSS-переменными темы
  • sv-41-svelte-ecosystem — Библиотеки компонентов экосистемы часто темизируются именно через CSS-переменные, которые разбираются в этом уроке
Стилизация: scoped-стили и CSS-переменные

0

1

Войти