CSS-in-JS глубоко: styled-components, Emotion, Linaria, vanilla-extract
CSS-in-JS — самый спорный подход в истории стилизации фронтенда. В 2016–2020 он казался будущим: стили живут рядом с компонентом, темизация — через контекст, мёртвый код удаляется сам, всё типизируется. В 2024–2026 его обвиняют в тормозах гидратации и избыточном рантайме. Обе стороны правы — в своих контекстах. Чтобы понимать, когда CSS-in-JS уместен, а когда нет, надо разобраться, как он работает под капотом: где выполняется стилизация (в браузере или на сборке), чем это оплачивается и какие проблемы он вообще решал.
В этой главе — полный разбор: от проблем «до», через runtime-реализации (styled-components, Emotion), к zero-runtime (Linaria, vanilla-extract), сравнительная таблица и честный ответ на вопрос «стоит ли брать сегодня». Глава намеренно длинная: это категория, которую ты гарантированно встретишь в legacy-коде, и понимание её механики — обязательная часть фундамента.
Что такое CSS-in-JS и какие проблемы решал
Заголовок раздела «Что такое CSS-in-JS и какие проблемы решал»Идея: стили описываются внутри JavaScript и привязываются к компоненту, а не живут в отдельном CSS-файле. В классическом runtime-варианте CSS генерируется в браузере во время рендеринга. Исторический контекст — болезни эпохи 2014–2016:
- Глобальное пространство имён CSS. Класс
.buttonв одном файле ломал.buttonв другом; БЭМ-конвенция (block__element--modifier) была обходом, а не решением. CSS-in-JS генерирует уникальные имена классов автоматически — изоляция бесплатно. - Мёртвый код. CSS растёт монолитно: удалил компонент — стили остались. В CSS-in-JS стили живут в JS-модуле компонента: удали импорт — стили не попадают в бандл (в идеальной реализации).
- Динамические стили. «Кнопка красная в состоянии ошибки, иначе серая» в CSS — борьба с классами-состояниями. В CSS-in-JS — обычный тернарный оператор над пропсами.
- Темизация и зависимость от пропсов. Токены темы как JS-объект, доступный в любом стиле через контекст. В чистом CSS 2016 года — только Custom Properties (ещё свежие) или CSS-переменные через препроцессоры.
- Критический CSS и SSR. Разметка рождается в JS — можно на сервере собрать использованные стили и вставить в
<style>первого ответа.
Эти проблемы решались реально — поэтому взлёт был стремительным. Расплата пришла позже, и о ней — в разборе под капотом.
styled-components: runtime-стилизация как стандарт
Заголовок раздела «styled-components: runtime-стилизация как стандарт»styled-components (2016, Мишель Байер / Глен Маддерн) — эталонный синтаксис категории: «компонент = тег + стилевая функция». Официальная документация покрывает API, темизацию и SSR.
npm i styled-componentsimport styled, { ThemeProvider } from 'styled-components';
// Базовый синтаксис: styled.тег`стили`const Title = styled.h1` font-size: 2rem; color: #1e293b; margin-bottom: 1rem;`;
// Интерполяция функции — стили зависят от пропсовconst Button = styled.button<{ $primary?: boolean }>` padding: 0.5rem 1rem; border: none; border-radius: 8px; cursor: pointer;
/* пропс $primary: $ — конвенция, чтобы styled-components не прокидывал его в DOM (transient props) */ background: ${(p) => (p.$primary ? '#7c3aed' : 'transparent')}; color: ${(p) => (p.$primary ? 'white' : '#7c3aed')}; border: 1px solid #7c3aed;
&:hover { background: ${(p) => (p.$primary ? '#6d28d9' : '#f5f3ff')}; }`;
// Композиция: styled(Компонент) наследует стилиconst IconButton = styled(Button)` padding: 0.5rem; border-radius: 50%;`;Под капотом на каждый рендер работает сериализатор: шаблонные строки компилируются Babel-плагином в массивы [strings[], interpolations[]], интерполяции вычисляются с пропсами, результат скармливается CSS-парсеру (stylis), который генерирует валидный CSS и заменяет вложенность на плоские селекторы. Из хэша CSS получается имя класса (sc-xyz123), правило вставляется в <style>-тег в <head> (или в SSR-стрим). Всё это — в рантайме, в браузере, при каждом первом рендере каждого уникального набора стилей.