본문 바로가기
3주차

Emotion CSS-in-JS

by 1jiwoo27 2025. 5. 2.

안녕하세요! AT SOPT YB 36기 엄지우입니다.

 

3주차 과제에서는 CSS 라이브러리를 꼭 하나씩 사용해야 했는데, 저는 지난 세미나에서 배운 Emotion CSS를 사용해보았습니다!

Emotion CSS를 사용하면서, 기존에는 따로 만들던 CSS 파일을 JS 파일 안에 합칠 수 있어 매우 편리하다고 느꼈어요.

이번 글에서는 실습하면서 사용했던 코드들을 가져와 Emotion의 주요 특징을 설명해보려 합니다 😺

* 아래에 파일명이 적힌 코드들은 실제로 제가 사용한 것이고, 그 외 코드는 예제입니다.

 

 

 

 

목차

  1. 왜 CSS-in-JS가 필요할까?
  2. Emotion이란?
  3. CSS props 사용법과 스타일 우선순위
  4. Composition (스타일 병합)
  5. 중첩 선택자
  6. 미디어쿼리
  7. Global Styles (글로벌 스타일)
  8. ThemeProvider & theme.js 사용법
  9. style 공유 방법
  10. 동적 스타일 → style prop 사용
  11. Keyframes (애니메이션)
  12. Labels (디버깅 편의성)

 

 

1. 왜 CSS-in-JS가 필요할까?

기존 CSS(SCSS 포함)는 컴포넌트 중심으로 개발하는 React 방식과 충돌하는 경우가 많아요.

 

대표적인 문제:

  • 스타일 충돌: 클래스명이 겹쳐서 의도치 않은 스타일 적용
  • 재사용 어려움: 컴포넌트에 따라 다르게 스타일링하고 싶은데 코드 분리가 어려움
  • 조건부 스타일링: props 값에 따라 동적으로 스타일링하는 것이 불편

그래서 등장한 방식이 CSS-in-JS입니다.

CSS-in-JS는 JS 파일 안에서 직접 스타일을 작성할 수 있게 해줘서 이런 문제를 해결해요.

 

2. Emotion이란?

Emotion은 대표적인 CSS-in-JS 라이브러리입니다.

styled-components와 비슷하지만,

  • 가볍고 빠른 성능
  • css props와 같은 다양한 스타일 방식 지원
  • TypeScript 친화적

이라는 장점들이 있어요.

 

Emotion은 다음 세 가지 패키지로 나눠집니다.

  • @emotion/css: 기본적인 css-in-js 유틸 제공
  • @emotion/react: css props, ThemeProvider, 글로벌 스타일 제공
  • @emotion/styled: styled-components처럼 스타일 작성 가능

 

✅ 설치

npm install @emotion/react @emotion/styled

💡 Tip: TypeScript 사용 시 타입 자동 지원!

 

3. CSS props 사용법과 스타일 우선순위

JSX Pragma 설정

css props를 사용하려면 파일 상단에 아래 코드를 추가해야 해요.

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

 

스타일 우선순위

  • 컴포넌트 스타일 > css props > 글로벌 스타일
  • CSS specificity(우선순위) 규칙을 따름

 

4. Composition (스타일 병합)

Emotion을 사용하면 스타일을 만들고 결합(Composition) 할 수 있어요.

Composition 기능을 사용하면 스타일이 적용 순서대로 병합되므로, 스타일 정의 순서를 고민할 필요가 없습니다.

const danger = css`
  color: red;
`;

const base = css`
  background-color: white;
  color: blue;
`;

return (
  <div>
    <div css={base}>이 텍스트는 파란색으로 표시됩니다.</div>
    <div css={[danger, base]}>
      이 텍스트도 파란색으로 표시됩니다. base 스타일이 danger 스타일을 덮어쓰기 때문입니다. 
    </div>
    <div css={[base, danger]}>이 텍스트는 빨간색으로 표시됩니다.</div>
  </div>
)

 

 

Composition 규칙

  • css 배열에서는 뒤에 오는 스타일이 앞의 스타일을 덮어쓴다.
    • ex) css={[danger, base] → base가 적용됨
    • ex) css={[base, danger] → danger가 적용됨

 

5. 중첩 선택자

상태 스타일

& 기호(중첩 선택자)를 사용하면 상태에 따른 스타일을 직관적으로 지정할 수 있어요.

const recentRemove = css`
  border: none;
  cursor: pointer;
  color:rgb(130, 130, 130);
  padding: 0;
  
 /* 중첩 선택자 사용 */
  &:hover {
    color: red;
  }
`;

Card.jsx

  • & 기호를 활용해 기존 선택자에 의존하지 않고 자연스럽게 상태나 구조별 스타일을 중첩할 수 있음
  • 전통적인 CSS의 .class:hover 없이 컴포넌트 스코프 안에서 깔끔하게 hover 효과 관리 가능

 

구조 스타일

자식 요소(p, span 등)에 대한 스타일도 컴포넌트 스코프 안에서 쉽게 정의할 수 있어요.

const container = css`
  padding: 20px;
  background-color: #f0f0f0;

  p {
    color: darkblue;
  }

  span {
    font-weight: bold;
  }
`;
  • container 스타일이 적용된 요소 내부:
    • p 태그: 글자색 darkblue
    • span 태그: 굵은 글씨

 

6. 미디어쿼리

재사용 가능한 미디어쿼리

반복적으로 사용되는 미디어쿼리(media query)를 변수로 만들어두면 코드 재사용성이 높아지고 유지보수가 쉬워져요.

const mq = '@media (min-width: 768px)';
const style = css`
  font-size: 14px;
  ${mq} {
    font-size: 18px;
  }
`;
  • 기본 스타일: 작은 화면 → font-size: 14px
  • 미디어쿼리 스타일: 큰 화면(768px 이상) → font-size: 18px

 

facepaint 사용

복잡한 미디어쿼리를 작성할 때 유용한 라이브러리입니다.

 

설치

npm install facepaint

 

facepaint는 Emotion과 함께 사용할 수 있으며, 여러 개의 브레이크포인트에 따라 스타일을 깔끔하게 작성할 수 있어요.

 

import facepaint from 'facepaint';
import { css } from '@emotion/react';

// 브레이크포인트 설정 (예: 모바일, 태블릿, 데스크탑)
const breakpoints = [576, 768, 992];
const mq = facepaint(breakpoints.map(bp => `@media (min-width: ${bp}px)`));

const box = css(
  mq({
    fontSize: ['14px', '16px', '18px'],
    padding: ['10px', '20px', '30px']
  })
);
  • mq({ ... }) 안에 속성별로 브레이크포인트별 값 배열 작성
  • 화면 크기에 따라 fontSizepadding 자동 적용

 

7. Global Styles (글로벌 스타일)

Emotion의 Global 컴포넌트를 사용하면 앱 전체에 적용할 전역 스타일을 설정할 수 있어요.

CSS 파일을 따로 만들지 않고도 전역 스타일을 쉽게 관리할 수 있습니다.

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

const globalStyles = css`
  html {
    scroll-behavior: smooth;
    font-size: 16px;
  }
  
  body {
    margin: 0;
    padding: 0;
    font-family: 'Arial', 'Apple SD Gothic Neo', 'Segoe UI', sans-serif;
    background-color:rgb(245, 245, 245);
    box-sizing: border-box;
  }
`;

App.jsx

  • Global 컴포넌트는 컴포넌트 트리 최상단(보통 App 컴포넌트)에 넣어서 사용
  • createGlobalStyle(styled-components 스타일)과 비슷한 역할

 

8. ThemeProvider & theme.js 사용법

프로젝트 규모가 커지면 색상, 폰트 크기 같은 스타일 값을 컴포넌트마다 일일이 적는 것은 비효율적이고 유지보수가 어렵습니다.
Emotion에서는 ThemeProvider와 별도의 theme.js 파일을 활용해 전역 스타일 값을 정의하고 쉽게 재사용할 수 있어요.

 

 

theme.js 파일 생성

export const theme = {
    colors: {
      primary: 'rgb(157, 196, 221)',
      point: 'rgb(245, 175, 175)',
    },
    fonts: {
       xl: "2rem",
       lg: "1.5rem",
       md: "1.2rem",
       ssm: "1.1rem",
       sm: "1rem",
       xs: "0.875rem", 	 	 
      }
  };

theme.js

  • 색상(colors)과 폰트 크기(fonts)를 객체 형태로 관리
  • 나중에 어디서든 불러와 사용 가능

 

ThemeProvider로 theme 적용

import { ThemeProvider } from '@emotion/react';
import { theme } from './styles/theme';
import App from './App'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>
  </StrictMode>,
)

main.jsx

  • ThemeProvider를 통해 theme 객체앱 전체에 전달
  • 하위 모든 컴포넌트에서 theme 사용 가능

 

styled 컴포넌트에서 theme 사용

const name = (theme) => css`
  margin-top: 1.5rem;
  font-size: ${theme.fonts.lg};
  font-weight: bold;
  cursor: pointer;
`;

Card.jsx

  • theme 값을 가져와서 스타일에 적용
  • theme.fonts.lg → theme에서 정의한 1.5rem 크기 사용

 

✔️ 장점

  • 색상, 폰트 크기 등 전역 스타일 관리 쉬움
  • 다크모드, 라이트모드 구현 시 편리
  • 유지보수 시 스타일 값 변경이 쉬움

 

9. style 공유 방법

Emotion에서는 스타일을 다른 컴포넌트에 쉽게 재사용할 수 있어요.
공통 스타일을 변수로 만들고 가져오는 방식으로 유지보수와 일관성을 높일 수 있습니다.

 

1) CSS 개체 내보내기

export const baseText = css`font-size: 16px;`;
  • css 함수를 사용해 공통 스타일을 변수로 선언
  • 필요한 컴포넌트 파일에서 import해서 사용 가능

 

2) 컴포넌트 재사용 스타일

const StyledButton = styled.button`
  ${baseText};
  padding: 10px;
`;
  • baseText 스타일을 버튼에 적용하고, 추가적인 스타일(패딩)을 덧붙임
  • 여러 컴포넌트에서 공통 스타일을 쉽게 공유하면서 필요에 따라 스타일을 확장 가능

 

10. 동적 스타일 → style prop 사용

컴포넌트의 props 값을 기반으로 스타일을 동적으로 변경할 수 있어요.

Emotion에서는 css 함수 또는 styled를 사용해 props에 따라 다양한 스타일을 적용할 수 있습니다.

const menubtn = (isActive, theme) => css`
    margin: 0;
    padding: 0.8rem;
    align-items: center;
    font-weight: ${isActive ? '500' : '400'};
    font-size: ${theme.fonts.sm};
    color: white;
    background-color: ${isActive ? theme.colors.point : theme.colors.primary};
    border-radius: 0.5rem;
    cursor: pointer;
    border: 1.5px solid white;
    transition: background-color 0.1s ease-in-out, font-weight 0.1s ease-in-out;
    font-family: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;

    &:hover {
        background-color: ${theme.colors.point};
    }
`;

Header.jsx

  • isActive라는 props 값으로 스타일 동적 변경
  • theme 값과 props를 조합해 디자인 일관성 유지 + 동적 스타일 구현

 

💡 Tip: 많은 속성을 props에 따라 바꾸고 싶다면 Emotion styled 사용

const Button = styled.button`
  background: ${({ active }) => (active ? 'green' : 'gray')};
`;
  • styled 방식에서는 props를 구조 분해 할당({ active })로 받아서 스타일에 사용
  • 코드가 깔끔하고 직관적이라 많은 속성을 props로 제어할 때 유리

 

11. Keyframes (애니메이션)

Emotion에서는 CSS keyframes를 그대로 사용할 수 있습니다.

스타일 안에 @keyframes을 정의해서 애니메이션 효과를 줄 수 있어요.

const spinner = css`
  border: 10px solid #f3f3f3;
  border-top: 10px solid #2d72d9;
  border-radius: 50%;
  width: 5rem;
  height: 5rem;
  animation: spin 1s linear infinite;
  margin-top: 5rem;

  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
`;

Spinner.jsx

  • animation 속성으로 spin 애니메이션을 1초마다 무한 반복
  • @keyframes spin으로 회전 효과(0도 → 360도) 정의

 

12. Labels (디버깅 편의성)

Emotion에서는 기본적으로 생성된 클래스명이 난수 형태(css-xxxxxx)로 만들어집니다.

babel-plugin-emotion을 사용하면 개발자 도구에서 직관적인 클래스 이름(라벨) 을 확인할 수 있어 디버깅이 훨씬 편리해요.

 

 

설치

npm install --save-dev @emotion/babel-plugin

 

babel.config.json 설정

// babel.config.json
{
  "plugins": ["@emotion"]
}
  • Babel 플러그인에 @emotion 추가
  • 개발 환경에서 Emotion이 컴포넌트명 또는 변수명 기반 클래스명 자동 생성

 

const myButton = css`
  background-color: blue;
`;

 

  • babel-plugin-emotion 미적용 → 개발자 도구에서 css-1a2b3c 형태로 보임
  • babel-plugin-emotion 적용 → 개발자 도구에서 myButton 라벨로 표시됨

 

 

 


 

🤙🏻 마무리하며

Emotion은 작은 프로젝트에도 잘 어울리지만, 특히 대규모 앱이나 동적 스타일링이 필요한 상황에서 그 진가를 발휘합니다.
효율적인 스타일 관리와 코드 재사용성을 높여주기 때문에 프로젝트가 커질수록 더욱 강력함을 느낄 수 있어요.

또한, 팀 프로젝트에서는 스타일 작성 방식(컨벤션) 을 초기에 정하고 일관되게 사용하는 것이 유지보수성과 협업 효율을 높이는 핵심입니다.

 

3주차 끝!