
React에서 스타일을 적용하는 방식은 여러 가지가 있다.
| 방식 | 특징 |
|---|---|
| CSS 파일 | 가장 기본적인 방식. 별도의 .css 파일에서 스타일을 관리한다. |
| CSS Module | 클래스명이 자동으로 고유해져서 스타일 충돌을 줄일 수 있다. |
| Sass/SCSS | 변수, 중첩, mixin 등을 사용할 수 있는 CSS 확장 문법이다. |
| styled-components | 컴포넌트 단위로 스타일을 작성할 수 있는 CSS-in-JS 라이브러리다. |
| Emotion | styled 방식과 css prop 방식을 모두 지원하는 CSS-in-JS 라이브러리다. |
| Tailwind CSS | 미리 정의된 유틸리티 클래스를 조합해서 스타일을 작성한다. |
이 중에서 내가 Emotion을 선택한 이유는 다음과 같다.
styled 방식과 css prop 방식을 모두 지원한다.Global, ThemeProvider 같은 React 스타일링 기능을 통해 전역 스타일과 테마 관리가 가능하다.기존에는 styled-components를 주로 사용했지만, 최근 maintenance mode로 전환되면서 새 프로젝트에서는 사용을 지양하고 있다.
이에 익숙한 사용 방식은 유지하면서도 더 유연한 스타일링이 가능한 Emotion을 선택하게 되었고, 특히 헷갈렸던 @emotion/react 부분을 중심으로 정리해보려고 한다.
Emotion은 CSS 파일을 따로 분리하지 않고, JavaScript 안에서 스타일을 작성할 수 있게 해주는 CSS-in-JS 라이브러리다.
일반 CSS 방식에서는 보통 이렇게 작성한다.
<button className="button">버튼</button>
.button {
padding: 10px 16px;
background-color: black;
color: white;
}
Emotion을 사용하면 스타일을 JavaScript 코드 안에서 작성할 수 있다.
import styled from "@emotion/styled";
const Button = styled.button`
padding: 10px 16px;
background-color: black;
color: white;
`;
function App() {
return <Button>버튼</Button>;
}
즉, Emotion은 컴포넌트와 스타일을 가까운 위치에서 관리할 수 있게 해준다.
이러한 방식은 styled-components를 사용해봤다면 익숙하게 느껴질 것이다.
Emotion을 사용하려면 보통 아래 두 패키지를 함께 설치한다.
npm install @emotion/react @emotion/styled
pnpm을 사용한다면 이렇게 설치할 수 있다.
pnpm add @emotion/react @emotion/styled
@emotion/styled와 @emotion/react는 무슨 차이가 있을까?
Emotion에는 대표적으로 두 패키지가 있다.
import styled from "@emotion/styled";
import { css } from "@emotion/react";
둘 다 Emotion이지만 역할이 조금 다르다.
| 패키지 | 역할 |
|---|---|
@emotion/styled | styled-components처럼 styled.div, styled.button 형태로 스타일 컴포넌트를 만들 때 사용한다. |
@emotion/react | css prop, Global, ThemeProvider, keyframes 같은 React 전용 기능을 제공한다. |
쉽게 말하면 다음과 같다.
@emotion/styled는 styled-components와 거의 비슷하게 사용하는 패키지다.
@emotion/react는 Emotion의 React 전용 핵심 기능을 제공하는 패키지다.
styled 방식은 styled-components와 매우 유사하기 때문에, 이번 글에서는 따로 설명하지 않겠다.
@emotion/react는 Emotion의 React 전용 기능을 제공한다.
대표적으로 다음 기능들이 있다.
cssGlobalThemeProviderkeyframescss는 Emotion 스타일을 만들 때 사용하는 함수다.
import { css } from "@emotion/react";
const containerStyle = css`
padding: 20px;
background-color: #f5f5f5;
`;
이렇게 만든 스타일은 JSX의 css prop에 넣어서 사용할 수 있다.
function App() {
return <div css={containerStyle}>내용</div>;
}
객체 형태로도 작성할 수 있다.
import { css } from "@emotion/react";
const containerStyle = css({
padding: "20px",
backgroundColor: "#f5f5f5",
});
문자열 방식은 일반 CSS를 쓰는 느낌이고, 객체 방식은 JavaScript 객체로 스타일을 작성하는 느낌이다.
Global은 전역 스타일을 지정할 때 사용한다.
컴포넌트 스타일은 보통 특정 컴포넌트에만 적용된다.
하지만 body, html, * 같은 전역 스타일은 특정 컴포넌트에만 묶기 어렵다.
이럴 때 Global을 사용할 수 있다.
import { Global, css } from "@emotion/react";
function App() {
return (
<>
<Global
styles={css`
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
}
button {
font-family: inherit;
}
`}
/>
<div>Emotion Global 스타일</div>
</>
);
}
Global은 보통 다음과 같은 경우에 사용한다.
body 기본 스타일font-family 기본값box-sizing예를 들어 프로젝트에서 공통 reset 스타일을 관리하고 싶다면 이런 식으로 분리할 수 있다.
// GlobalStyle.jsx
import { Global, css } from "@emotion/react";
const globalStyle = css`
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
background-color: #ffffff;
}
button {
border: none;
background: none;
cursor: pointer;
font-family: inherit;
}
`;
function GlobalStyle() {
return <Global styles={globalStyle} />;
}
export default GlobalStyle;
그리고 App에서 불러와 사용한다.
import GlobalStyle from "./GlobalStyle";
function App() {
return (
<>
<GlobalStyle />
<div>앱 화면</div>
</>
);
}
export default App;
ThemeProvider는 공통 디자인 값을 관리할 때 사용한다.
프로젝트를 하다 보면 색상, 폰트 크기, 간격 같은 값을 여러 곳에서 반복해서 사용하게 된다.
color: #333333;
background-color: #f5f5f5;
이런 값을 컴포넌트마다 직접 작성하면 나중에 수정하기 어렵다.
예를 들어 메인 컬러가 바뀌면 모든 파일을 찾아다니며 수정해야 한다.
이럴 때 theme을 만들어 공통 값을 관리할 수 있다.
const theme = {
colors: {
primary: "#2f80ed",
text: "#333333",
background: "#f5f5f5",
},
fontSize: {
small: "12px",
medium: "16px",
large: "24px",
},
};
그리고 ThemeProvider로 감싸준다.
import { ThemeProvider } from "@emotion/react";
import styled from "@emotion/styled";
const theme = {
colors: {
primary: "#2f80ed",
text: "#333333",
},
};
const Title = styled.h1`
color: ${({ theme }) => theme.colors.primary};
`;
function App() {
return (
<ThemeProvider theme={theme}>
<Title>Emotion ThemeProvider</Title>
</ThemeProvider>
);
}
export default App;
이렇게 하면 하위 컴포넌트에서 theme 값을 사용할 수 있다.
css prop 방식에서도 사용할 수 있다.
/** @jsxImportSource @emotion/react */
import { css, ThemeProvider } from "@emotion/react";
const theme = {
colors: {
primary: "#2f80ed",
},
};
const titleStyle = (theme) => css`
color: ${theme.colors.primary};
`;
function Title() {
return <h1 css={titleStyle}>Emotion</h1>;
}
function App() {
return (
<ThemeProvider theme={theme}>
<Title />
</ThemeProvider>
);
}
ThemeProvider를 사용하면 디자인 값을 한 곳에서 관리할 수 있다는 장점이 있다.
keyframes는 애니메이션을 만들 때 사용한다.
CSS에서 @keyframes를 작성하는 것처럼, Emotion에서도 애니메이션을 정의할 수 있다.
import { keyframes, css } from "@emotion/react";
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const boxStyle = css`
width: 100px;
height: 100px;
background-color: black;
animation: ${fadeIn} 1s ease-in-out;
`;
function App() {
return <div css={boxStyle} />;
}
이렇게 하면 컴포넌트 안에서 애니메이션까지 함께 관리할 수 있다.
Emotion을 사용할 때는 styled 방식과 css prop 방식을 상황에 맞게 구분해서 사용하는 것이 중요하다.
css prop은 JSX 요소에 바로 스타일을 작성할 수 있어서 간단한 스타일을 적용할 때 편리하다. 하지만 모든 스타일을 css prop으로 작성하면 JSX 안에 스타일 코드가 많아져 오히려 구조를 파악하기 어려워질 수 있다.
따라서 재사용되는 UI나 스타일 코드가 길어지는 경우에는 styled 방식으로 분리하고, 한 번만 사용하는 간단한 스타일에는 css prop을 사용하는 것이 좋다.
정리하면 다음과 같이 사용할 수 있다.
| 상황 | 추천 방식 |
|---|---|
| 여러 곳에서 재사용되는 버튼, 카드, 모달 | styled |
| 한 번만 사용하는 간단한 스타일 | css prop |
| reset CSS, body 기본 스타일 | Global |
| 공통 색상, 폰트, 간격 관리 | ThemeProvider |
처음 Emotion을 접했을 때는 @emotion/styled와 @emotion/react가 나뉘어 있어서 구조 자체가 조금 낯설고 헷갈렸다. 특히 css prop 방식은 기존에 사용하던 방식과 달라서 어떻게 활용해야 할지 감이 잘 잡히지 않았다.
이번에 개념을 정리하면서 각각의 역할을 구분해보니, 생각보다 단순한 구조라는 것을 이해할 수 있었다. 상황에 따라 styled와 css prop을 선택해서 사용할 수 있다는 점도 Emotion의 큰 장점이라고 느껴졌다.
앞으로는 단순히 사용하는 것을 넘어서, 상황에 맞게 스타일링 방식을 구분하면서 Emotion을 더 적극적으로 활용해볼 수 있을 것 같다.