Emotion — библиотека CSS-in-JS

16 июня 2026
Автор

Олег Марков

Введение

Emotion — одна из самых популярных CSS-in-JS библиотек в экосистеме React. Она выделяется двумя ключевыми особенностями: высокой производительностью и двумя способами написания стилей. Вы можете использовать css prop непосредственно на JSX-элементах или создавать стилизованные компоненты через styled API — похожий на Styled Components.

В отличие от многих конкурентов, Emotion активно применяется в известных проектах: MUI (Material UI) использует Emotion как основной движок стилей начиная с v5. Это говорит о надёжности и зрелости библиотеки.

В этой статье вы узнаете, как установить Emotion, какие подходы к стилизации она предлагает и как использовать продвинутые возможности: темизацию, анимации и SSR.

Установка

Emotion поставляется в двух основных пакетах:

# Базовый пакет — работает в любом окружении
npm install @emotion/react @emotion/styled

# Если используете Create React App или Vite
npm install @emotion/react @emotion/styled @emotion/babel-plugin

Для поддержки css prop без импорта pragma нужен Babel-плагин или настройка jsxImportSource:

// .babelrc
{
  "plugins": ["@emotion/babel-plugin"]
}

Или в tsconfig.json / jsconfig.json:

{
  "compilerOptions": {
    "jsxImportSource": "@emotion/react"
  }
}

Для Vite:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      jsxImportSource: '@emotion/react',
      babel: {
        plugins: ['@emotion/babel-plugin'],
      },
    }),
  ],
});

CSS Prop — первый способ стилизации

css prop — уникальная особенность Emotion, которой нет в большинстве других CSS-in-JS библиотек. Он позволяет писать стили прямо на элементе:

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

// Способ 1: строка CSS
function Button() {
  return (
    <button
      css={css`
        background-color: #6c63ff;
        color: white;
        padding: 12px 24px;
        border: none;
        border-radius: 8px;
        font-size: 16px;
        cursor: pointer;

        &:hover {
          background-color: #5a52d5;
        }
      `}
    >
      Нажми меня
    </button>
  );
}

// Способ 2: объект стилей
function Card() {
  return (
    <div
      css={{
        backgroundColor: 'white',
        borderRadius: '8px',
        padding: '24px',
        boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
      }}
    >
      Карточка
    </div>
  );
}

Вынос стилей в переменные

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

// Определяем стили отдельно — переиспользуемые
const baseButtonStyles = css`
  padding: 12px 24px;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.2s ease;
`;

const primaryStyles = css`
  background-color: #6c63ff;
  color: white;

  &:hover {
    background-color: #5a52d5;
    transform: translateY(-1px);
  }
`;

const dangerStyles = css`
  background-color: #e53e3e;
  color: white;

  &:hover {
    background-color: #c53030;
  }
`;

// Композиция стилей через массив
function App() {
  return (
    <div>
      <button css={[baseButtonStyles, primaryStyles]}>Основная</button>
      <button css={[baseButtonStyles, dangerStyles]}>Опасная</button>
    </div>
  );
}

Массив стилей — мощный инструмент Emotion: стили применяются слева направо, более поздние переопределяют предыдущие. Можно передавать false для условного включения стилей:

function Button({ disabled, primary }) {
  return (
    <button
      css={[
        baseButtonStyles,
        primary && primaryStyles,
        disabled && css`
          opacity: 0.5;
          cursor: not-allowed;
          pointer-events: none;
        `,
      ]}
    >
      Кнопка
    </button>
  );
}

Styled API — второй способ стилизации

Если вы знакомы со Styled Components, @emotion/styled будет привычен — синтаксис практически идентичен:

import styled from '@emotion/styled';

const Button = styled.button`
  background-color: #6c63ff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;

  &:hover {
    background-color: #5a52d5;
  }
`;

// Динамические стили через пропсы
const Badge = styled.span`
  display: inline-block;
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 600;
  background-color: ${props => {
    switch (props.variant) {
      case 'success': return '#48bb78';
      case 'error': return '#e53e3e';
      case 'warning': return '#ed8936';
      default: return '#6c63ff';
    }
  }};
  color: white;
`;

// TypeScript версия
interface CardProps {
  elevated?: boolean;
}

const Card = styled.div<CardProps>`
  background: white;
  border-radius: 8px;
  padding: 24px;
  box-shadow: ${props => props.elevated
    ? '0 10px 40px rgba(0, 0, 0, 0.15)'
    : '0 2px 8px rgba(0, 0, 0, 0.05)'
  };
`;

Темизация

Emotion имеет встроенную поддержку тем через ThemeProvider:

import { ThemeProvider } from '@emotion/react';

const theme = {
  colors: {
    primary: '#6c63ff',
    secondary: '#48bb78',
    background: '#f7f8fc',
    text: '#2d3748',
    error: '#e53e3e',
  },
  spacing: (factor) => `${8 * factor}px`,
  breakpoints: {
    sm: '576px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Layout />
    </ThemeProvider>
  );
}

Доступ к теме в стилях:

import styled from '@emotion/styled';
import { css, useTheme } from '@emotion/react';

// В styled API
const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: white;
  padding: ${props => props.theme.spacing(1.5)} ${props => props.theme.spacing(3)};
`;

// В css prop
function Card() {
  const theme = useTheme();
  
  return (
    <div
      css={css`
        background: ${theme.colors.background};
        padding: ${theme.spacing(3)};
        
        @media (min-width: ${theme.breakpoints.md}) {
          padding: ${theme.spacing(4)};
        }
      `}
    >
      Содержимое карточки
    </div>
  );
}

Для TypeScript расширяем тип темы:

// emotion.d.ts
import '@emotion/react';

declare module '@emotion/react' {
  export interface Theme {
    colors: {
      primary: string;
      secondary: string;
      background: string;
      text: string;
      error: string;
    };
    spacing: (factor: number) => string;
    breakpoints: {
      sm: string;
      md: string;
      lg: string;
      xl: string;
    };
  }
}

Глобальные стили

import { Global, css } from '@emotion/react';

function App() {
  return (
    <>
      <Global
        styles={css`
          *, *::before, *::after {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
          }

          body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background-color: #f7f8fc;
            color: #2d3748;
          }
        `}
      />
      <Router>
        <Layout />
      </Router>
    </>
  );
}

Анимации

import { css, keyframes } from '@emotion/react';

const bounce = keyframes`
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-20px);
  }
`;

const pulseAnimation = keyframes`
  0% {
    box-shadow: 0 0 0 0 rgba(108, 99, 255, 0.4);
  }
  70% {
    box-shadow: 0 0 0 10px rgba(108, 99, 255, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(108, 99, 255, 0);
  }
`;

const BouncingBall = styled.div`
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: #6c63ff;
  animation: ${bounce} 1s ease infinite;
`;

const PulseButton = styled.button`
  background-color: #6c63ff;
  color: white;
  border: none;
  border-radius: 50%;
  width: 60px;
  height: 60px;
  cursor: pointer;
  animation: ${pulseAnimation} 2s ease infinite;
`;

Серверный рендеринг

В Next.js App Router Emotion требует специальной настройки для корректного SSR:

// app/registry.tsx
'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

export default function StyledComponentsRegistry({ children }) {
  const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: 'css' });
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted = [];
    cache.insert = (...args) => {
      const serialized = args[1];
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push(serialized.name);
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const names = flush();
    if (names.length === 0) return null;
    let styles = '';
    for (const name of names) {
      styles += cache.inserted[name];
    }
    return (
      <style
        key={cache.key}
        data-emotion={`${cache.key} ${names.join(' ')}`}
        dangerouslySetInnerHTML={{ __html: styles }}
      />
    );
  });

  return <CacheProvider value={cache}>{children}</CacheProvider>;
}

// app/layout.tsx
import StyledComponentsRegistry from './registry';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          {children}
        </StyledComponentsRegistry>
      </body>
    </html>
  );
}

Emotion vs Styled Components

Характеристика Emotion Styled Components
CSS prop ✅ Есть ❌ Нет
styled API ✅ Есть ✅ Есть
Производительность Немного быстрее Немного медленнее
Размер бандла ~11KB ~15KB
SSR Требует настройки Лучше из коробки
TypeScript Отличная поддержка Отличная поддержка
Используется в MUI, Chakra UI Множество проектов

Итоги

Emotion — мощная и гибкая библиотека CSS-in-JS, которая предлагает два подхода к стилизации: css prop для тех, кто предпочитает писать стили рядом с JSX, и styled API для тех, кто привык к компонентному подходу. Высокая производительность, отличная TypeScript-поддержка и использование в MUI делают Emotion надёжным выбором для React-проектов.

Стрелочка влевоДинамические стили в ReactСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий