Олег Марков
Scoped стили - изоляция CSS в компонентах
Введение
Scoped стили (часто пишут как scoped-styles) — это подход к написанию CSS, при котором стили применяются только к определенному фрагменту разметки, обычно к одному компоненту. Они не «просачиваются» наружу и не затрагивают другие части приложения.
Если вы когда-либо:
- меняли один класс в CSS и внезапно ломали внешний вид на другой странице;
- тратили время на изобретение сложных правил именования классов;
- боялись рефакторить старые стили,
то scoped стили — это как раз тот инструмент, который помогает справиться с этими проблемами.
Давайте спокойно разберем, что скрывается за идеей scoped-styles, какие есть подходы в браузере и во фреймворках, и как применить это на практике.
Что такое scoped стили и зачем они нужны
Основная идея scoped-styles
Смотрите, логика довольно простая: у вас есть компонент (часть интерфейса) и набор стилей, которые относятся только к нему. Вы явно говорите браузеру или сборщику — «эти стили работают только внутри этого компонента».
Цели такие:
Изоляция
Стили компонента не влияют на остальную страницу.Предсказуемость
Вы уверены, что изменение стиля кнопки в компонентеProfileCardне изменит кнопку вCheckoutForm.Упрощенный рефакторинг
Можно смело переименовывать классы и реорганизовывать разметку внутри компонента.Масштабируемость
Команда может параллельно работать над разными компонентами без постоянных конфликтов в CSS.
Проблемы «глобального» CSS
До появления популярных компонентных подходов к разработке использовался в основном глобальный CSS. То есть:
- вы подключаете один или несколько
.cssфайлов; - селекторы в них применяются ко всей странице;
- порядок подключения влияет на приоритет стилей.
Это ведет к типичным проблемам:
- «магические» переопределения (
!important, усиление специфичности); - стили, которые случайно задевают лишние элементы;
- сложность удаления неиспользуемого CSS.
Scoped стили решают это, ограничивая область действия стилей.
Основные подходы к scoped-styles
Scoped стили — не одна конкретная технология, а общий принцип. Реализовать его можно по-разному:
- Через фреймворки (Vue, Svelte и др.).
- Через CSS-in-JS (Styled Components, Emotion, JSS и т.д.).
- Через нативные веб-компоненты и Shadow DOM.
- Через CSS Modules на уровне сборки.
- Через новые нативные возможности типа
@scope(экспериментально).
Теперь по очереди разберем эти подходы на практических примерах.
Scoped стили во фреймворках
Scoped стили во Vue
Во Vue 2/3 есть встроенная поддержка scoped стилей в .vue файлах. Давайте посмотрим, как это выглядит.
Базовый пример
Представьте простой компонент Button.vue:
<template>
<button class="btn">Нажмите меня</button>
</template>
<script>
// Здесь мы экспортируем обычный Vue компонент
export default {
name: 'AppButton'
}
</script>
<style scoped>
/* Эти стили будут применяться только к шаблону этого компонента */
.btn {
background-color: #3490dc; /* Синий фон */
color: white; /* Белый текст */
padding: 8px 16px; /* Внутренние отступы */
border-radius: 4px; /* Скругленные углы */
}
</style>
Ключевой момент — атрибут scoped у тега <style>.
Vue при сборке:
- добавляет специальный уникальный атрибут ко всем элементам шаблона компонента, например
data-v-123abc; - переписывает CSS-селекторы, чтобы они включали этот атрибут.
То есть ваш селектор .btn превращается во что-то вроде:
.btn[data-v-123abc] {
/* стили компонента */
}
Именно поэтому стили не затрагивают кнопки в других компонентах.
Как стилизовать потомков и вложенные элементы
Если вы пишете:
<style scoped>
.card {
border: 1px solid #ddd;
}
/* Селектор потомка тоже будет "обрезан" для этого компонента */
.card .title {
font-weight: bold;
}
</style>
Vue перепишет это примерно так:
.card[data-v-123abc] {
border: 1px solid #ddd;
}
.card[data-v-123abc] .title {
font-weight: bold;
}
То есть вся цепочка селекторов остается, но ограничивается атрибутом компонента.
Глобальные стили внутри компонента Vue
Иногда вам нужно добавить глобальный стиль из компонента, например, стилизовать тег body или внешний контейнер.
Для этого Vue предлагает :global:
<style scoped>
/* Локальный стиль — только для этого компонента */
.wrapper {
padding: 16px;
}
/* Глобальный стиль — применяется ко всему приложению */
:global(body) {
margin: 0; /* Сбрасываем отступы у body */
font-family: sans-serif; /* Общий шрифт */
}
</style>
Здесь :global(body) не будет переписан с атрибутом data-v-... и станет обычным глобальным селектором body.
Стилизация дочерних компонентов во Vue
Частый вопрос — как изменить стили дочернего компонента, если у обоих scoped стили.
Допустим, у вас есть:
<!-- Parent.vue -->
<template>
<div class="parent">
<ChildComponent class="child-wrapper" />
</div>
</template>
<style scoped>
.parent {
border: 1px solid red; /* Стили родителя */
}
</style>
<!-- ChildComponent.vue -->
<template>
<div class="child">
Дочерний контент
</div>
</template>
<style scoped>
.child {
color: blue; /* Стили дочернего компонента */
}
</style>
По умолчанию scoped-стили родителя не залезают внутрь ChildComponent. Чтобы нацелиться на внутреннюю разметку дочернего компонента, во Vue 3 используется псевдо-селектор :deep:
<style scoped>
/* Этот стиль применится к .child внутри ChildComponent */
:deep(.child) {
color: green; /* Переопределяем цвет дочернего компонента */
}
</style>
Здесь Vue оставит селектор .child без добавления атрибута, и он станет глобальным. Но благодаря специфичности и каскаду вы можете контролировать приоритет.
Scoped стили в Svelte
В Svelte схожий принцип, но атрибут scoped в style не нужен — изоляция включена по умолчанию.
Пример компонента Svelte
<!-- Button.svelte -->
<script>
// Здесь мы объявляем проп "label"
export let label = 'Кнопка';
</script>
<button class="btn">{label}</button>
<style>
/* Эти стили автоматически будут scoped для этого компонента */
.btn {
background-color: #38c172; /* Зеленый фон */
color: white; /* Белый текст */
border-radius: 4px; /* Скругленные углы */
padding: 8px 16px; /* Отступы */
}
</style>
Svelte тоже добавляет к разметке и селекторам уникальный атрибут, например class="btn svelte-1a2b3c", и переписывает CSS-селекторы соответственно.
Глобальные стили в Svelte
Чтобы добавить глобальный стиль, используется псевдо-класс :global:
<style>
/* Локальный стиль для компонента */
.wrapper {
padding: 16px;
}
/* Глобальный стиль */
:global(body) {
margin: 0; /* Сбрасываем отступы */
font-family: system-ui; /* Общий шрифт */
}
/* Можно глобализовать класс */
:global(.app-container) {
max-width: 1200px; /* Максимальная ширина */
margin: 0 auto; /* Центрирование */
}
</style>
Scoped стили через CSS Modules
CSS Modules — это подход, при котором каждый .module.css файл рассматривается как набор локальных стилей. Его часто используют в React, Next.js и других сборках на основе Webpack / Vite.
Как это работает концептуально
- Каждый класс внутри CSS Module «хэшируется» во время сборки.
- В JSX/TSX вы импортируете объект, где ключ — имя из CSS, а значение — уникальное сгенерированное имя.
- В результате классы не конфликтуют между файлами.
Пример с React
Давайте разберем простой пример.
Создадим файл Button.module.css:
/* Button.module.css */
/* Это локальный класс, он не попадет в глобальный scope как есть */
.btn {
background-color: #4dc0b5; /* Цвет фона */
color: white; /* Цвет текста */
border-radius: 4px; /* Скругленные углы */
padding: 8px 16px; /* Внутренние отступы */
}
Теперь компонент Button.tsx:
// Button.tsx
import React from 'react';
// Импортируем объект со сгенерированными именами классов
import styles from './Button.module.css';
type ButtonProps = {
label: string; // Текст кнопки
};
export const Button: React.FC<ButtonProps> = ({ label }) => {
return (
// Применяем локальный класс из CSS модуля
<button className={styles.btn}>
{label}
</button>
);
};
Во время сборки:
.btnбудет переименован, например, вButton_btn__3XadD;styles.btnбудет содержать именно это сгенерированное имя.
Таким образом, даже если в другом модуле есть класс .btn, они не пересекутся.
Комбинирование классов
Иногда нужно комбинировать несколько классов из одного или разных модулей:
import styles from './Button.module.css';
import layout from './Layout.module.css';
export const Button = ({ label }) => {
return (
<button
className={
styles.btn + ' ' + layout.inlineBlock
// Здесь мы соединяем два разных класса через пробел
}
>
{label}
</button>
);
};
Чаще для этого используют утилиты наподобие clsx, но логика остается той же.
CSS-in-JS и scoped стили
CSS-in-JS — это подход, когда вы описываете стили прямо в JavaScript/TypeScript. Библиотеки (Styled Components, Emotion, JSS и др.) автоматически создают уникальные классы и подставляют их в DOM.
Styled Components — пример scoped-styles
Покажу вам на примерe Styled Components, так как он очень наглядный.
// Button.tsx
import React from 'react';
import styled from 'styled-components';
// Создаем компонент со стилями
const ButtonRoot = styled.button`
/* Эти стили будут применяться только к этому компоненту */
background-color: #9561e2; /* Фиолетовый фон */
color: white; /* Белый текст */
border-radius: 4px; /* Скругленные углы */
padding: 8px 16px; /* Отступы */
/* Ховер состояние */
&:hover {
background-color: #794acf; /* Более темный фиолетовый при наведении */
}
`;
type ButtonProps = {
label: string; // Текст кнопки
};
export const Button: React.FC<ButtonProps> = ({ label }) => {
return (
// Используем стилизованный компонент
<ButtonRoot>
{label}
</ButtonRoot>
);
};
Библиотека:
- генерирует уникальный класс, например
.sc-bcXHqe; - добавляет нужные правила в
<style>в<head>; - применяет класс к
button.
Все это создает scoped стили: ваши правила не конфликтуют с другими компонентами.
Динамические стили (зависящие от пропсов)
Scoped-styles в CSS-in-JS легко сочетаются с логикой компонента.
import styled from 'styled-components';
type ButtonRootProps = {
primary?: boolean; // Флаг — главное действие или нет
};
const ButtonRoot = styled.button<ButtonRootProps>`
padding: 8px 16px; /* Общие отступы */
border-radius: 4px; /* Скругление углов */
color: white; /* Белый текст */
/* Цвет фона зависит от пропса */
background-color: ${({ primary }) =>
primary ? '#38c172' : '#6c757d'};
// Если primary true - зеленый, иначе серый
`;
// Использование:
// <ButtonRoot primary>Сохранить</ButtonRoot>
// <ButtonRoot>Отмена</ButtonRoot>
Стили остаются scoped, даже если зависят от пропсов.
Scoped стили через веб-компоненты и Shadow DOM
Теперь давайте взглянем на нативный способ добиться полной изоляции — Shadow DOM.
Что такое Shadow DOM
Shadow DOM — это отдельное дерево DOM-элементов, прикрепленное к хост-элементу. Внутри него:
- стили не «протекают» наружу;
- глобальные стили страницы не влияют на внутренние элементы (за исключением некоторых свойств по наследованию, например
font-family).
По сути это идеальный вариант scoped-styles на уровне браузера.
Пример простого веб-компонента с Shadow DOM
Давайте разберем пример на чистом JavaScript.
// Определяем новый веб-компонент
class MyButton extends HTMLElement {
constructor() {
super(); // Вызываем конструктор базового класса
// Создаем Shadow DOM в режиме closed или open
const shadow = this.attachShadow({ mode: 'open' });
// mode: 'open' - позволяет обращаться к shadowRoot снаружи
// mode: 'closed' - скрывает shadowRoot от внешнего доступа
// Создаем кнопку
const button = document.createElement('button');
button.textContent = 'Кликните меня'; // Текст на кнопке
// Создаем элемент <style> для scoped стилей
const style = document.createElement('style');
style.textContent = `
/* Эти стили применяются ТОЛЬКО внутри shadow DOM компонента */
button {
background-color: #f6993f; /* Оранжевый фон */
color: white; /* Белый текст */
border-radius: 4px; /* Скругленные углы */
padding: 8px 16px; /* Отступы */
}
`;
// Добавляем стили и кнопку в shadow root
shadow.appendChild(style);
shadow.appendChild(button);
}
}
// Регистрируем элемент <my-button>
customElements.define('my-button', MyButton);
Теперь, если вы вставите в HTML:
<my-button></my-button>
То:
- внутри будет
button, стилизованный по правилам выше; - внешний CSS страницы, вроде:
button {
background-color: red; /* Глобальное правило для всех кнопок */
}
не повлияет на кнопку внутри <my-button>.
Это полноценный, нативный scoped CSS.
Новый нативный CSS @scope (экспериментально)
Современные браузеры начинают внедрять нативное средство для ограничения области действия стилей — директиву @scope.
Важно: поддержка пока не во всех браузерах и может меняться. Но идея полезна, стоит понимать ее концептуально.
Общая идея @scope
Синтаксис может выглядеть так:
@scope (.card) {
/* Эти стили применяются только внутри элементов с классом .card */
h2 {
font-size: 20px; /* Размер заголовка внутри .card */
}
.button {
padding: 8px 12px; /* Отступы у .button внутри .card */
}
}
То есть:
- вы задаете область (scope) —
.card; - все селекторы внутри работают только для этой области и ее потомков.
В разметке:
<div class="card">
<h2>Заголовок в карточке</h2> <!-- Подпадает под scope -->
<button class="button">Кнопка</button> <!-- Подпадает под scope -->
</div>
<h2>Глобальный заголовок</h2> <!-- Не стилизуется через @scope -->
<button class="button">Глобальная кнопка</button> <!-- Тоже не попадает -->
Когда @scope станет широко поддерживаться, это даст возможность реализовать scoped-styles без сборщиков и фреймворков, просто средствами CSS. Но пока в продакшене полагаться на это рано.
Сравнение подходов к scoped-styles
Чтобы вам было проще сориентироваться, давайте коротко сравним основные варианты.
Фреймворки (Vue, Svelte и др.)
Плюсы:
- простое включение scoped стилей прямо в компонентах;
- минимум конфигурации;
- хорошо интегрировано с экосистемой.
Минусы:
- привязано к конкретному фреймворку;
- миграция на другую платформу потребует переноса стилей.
CSS Modules
Плюсы:
- независимы от фреймворка (чаще всего React, но можно и в других сборках);
- понятный подход через импорт модулей;
- отсутствие глобальных конфликтов по умолчанию.
Минусы:
- чуть более многословный синтаксис (нужно импортировать
styles); - не позволяют использовать всю мощь CSS-in-JS (например, вычислимые значения прямо в стиле).
CSS-in-JS
Плюсы:
- полная динамика: можно использовать пропсы, состояние и т.п.;
- хорошие варианты для темизации (
ThemeProvider); - стили хранятся рядом с логикой компонента.
Минусы:
- накладные расходы на рантайм (у некоторых библиотек);
- сложнее настройка, чем у обычного CSS;
- требует внимательного отношения к производительности.
Shadow DOM (веб-компоненты)
Плюсы:
- настоящая нативная изоляция на уровне браузера;
- надежно защищает от конфликтов со стилями снаружи;
- можно использовать без сборщиков.
Минусы:
- не всегда удобно пробрасывать стили извне;
- потребуется учитывать особенности работы с
::part,::slottedи кастомными свойствами; - экосистема меньше, чем у крупных фреймворков.
Практические советы по работе со scoped-styles
1. Локальные по умолчанию, глобальные — осознанно
Хорошая стратегия — считать стили локальными по умолчанию, а глобальные вводить только когда есть четкая потребность.
Пример:
- локальные стили — через scoped (Vue, Svelte), CSS Modules или CSS-in-JS;
- глобальные:
- сбросы / нормализация;
- базовая типографика (
body,html); - общие layout-контейнеры.
В Vue/Svelte это значит: вы в основном работаете с <style scoped>/локальным <style>, а :global используете редко и преднамеренно.
2. Не злоупотребляйте вложенностью селекторов
Даже с scoped-styles сохраняется специфика CSS. Если вы начинаете писать:
.card .header .title .icon {
/* очень длинный селектор */
}
это может усложнить поддержку. Лучше:
- вводить ясные «ролевая» классы;
- не полагаться на слишком глубокую структуру DOM.
3. Используйте дизайн-систему поверх scoped-styles
Scoped стили отлично сочетаются с дизайн-системой:
- цветовые переменные (CSS custom properties или тема в CSS-in-JS);
- общие отступы, сетки, типографика.
Вместо того чтобы дублировать одни и те же значения в каждом компоненте, выносите их в один слой, а scoped-стили используйте для структуры конкретного компонента.
4. Внимательно относитесь к производительности
Некоторые подходы (особенно CSS-in-JS) могут создавать дополнительные <style> элементы и вычислять стили на лету.
Рекомендации:
- профилируйте приложение, если компонентов много;
- используйте библиотечные механизмы для статической оптимизации (например, Babel-плагины для Styled Components);
- избегайте слишком сложных динамических вычислений в стилях.
Заключение
Подход scoped-styles — это не отдельная технология, а общий способ организовать CSS так, чтобы он был предсказуемым, изолированным и удобным в поддержке. Сегодня у вас есть несколько зрелых вариантов реализации:
- scoped стили, встроенные во фреймворки (Vue, Svelte);
- CSS Modules в связке с React и другими сборщиками;
- CSS-in-JS библиотеки, которые дают максимум гибкости и динамики;
- нативный Shadow DOM для веб-компонентов;
- перспективная нативная директива
@scope, которая постепенно появляется в браузерах.
Выбирая конкретный подход, вы ориентируетесь на стек проекта, требования к производительности и привычки команды. Но сама идея scoped-styles — изолировать стили на уровне компонентов и уменьшить влияние глобального CSS — уже стала стандартом в современном фронтенде.
Частозадаваемые технические вопросы по теме scoped-styles
Как переопределить scoped стили из внешнего компонента во Vue без :deep?
Если вы не хотите использовать :deep, вы можете:
- Вынести нужную часть стилей дочернего компонента в глобальный класс (через
:global(.some-class)). - В родительском компоненте переопределить
.some-classобычным глобальным CSS с более высокой специфичностью. - Либо передавать пропы для управления модификаторами (например,
:class="{ active: isActive }") и стилизовать только эти модификаторы в родителе.
Как комбинировать scoped стили и CSS Variables (custom properties)?
- Объявляйте переменные на уровне
:rootили крупного контейнера:css :root { --primary-color: #38c172; /* Базовый цвет */ } - В scoped-стиле компонента используйте
var(--primary-color).
Переменные прекрасно проходят через границы scoped стилей, так как работают по механизму наследования, а не по селекторам.
Как стилизовать содержимое слота во веб-компоненте с Shadow DOM?
- Внутри веб-компонента используйте псевдо-элемент
::slotted:css ::slotted(button) { /* Стили для переданного в слот <button> */ padding: 8px 16px; } - Помните, что
::slottedработает только с верхним уровнем элементов, а не с произвольной глубиной вложенности.
Как настроить TypeScript для корректной работы с CSS Modules?
- Добавьте декларацию модулей, например
global.d.ts:ts declare module '*.module.css' { const classes: { [key: string]: string }; // Карта классов export default classes; } - Убедитесь, что файл деклараций включен в
tsconfig.jsonчерезincludeилиfiles. - После этого импорт вида
import styles from './Button.module.css';будет типобезопасным.
Как избежать «дублирования» стилей в разных scoped компонентах?
- Введите слой общих утилит: глобальный файл с утилитарными классами (
.flex,.mt-2, и т.п.) или токенами дизайна. - Подключайте эти классы в разметке компонента, а scoped-стили используйте для специфичных вещей.
- В CSS-in-JS вынесите повторяющиеся фрагменты в базовые стилизованные компоненты (например,
BaseButton) и наследуйте их в более конкретных компонентах (PrimaryButton,SecondaryButton).
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев