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-переменные, которые разбираются в этом уроке