안녕하세요! AT SOPT YB 36기 엄지우입니다.
3주차 과제에서는 CSS 라이브러리를 꼭 하나씩 사용해야 했는데, 저는 지난 세미나에서 배운 Emotion CSS를 사용해보았습니다!
Emotion CSS를 사용하면서, 기존에는 따로 만들던 CSS 파일을 JS 파일 안에 합칠 수 있어 매우 편리하다고 느꼈어요.
이번 글에서는 실습하면서 사용했던 코드들을 가져와 Emotion의 주요 특징을 설명해보려 합니다 😺
* 아래에 파일명이 적힌 코드들은 실제로 제가 사용한 것이고, 그 외 코드는 예제입니다.
목차
- 왜 CSS-in-JS가 필요할까?
- Emotion이란?
- CSS props 사용법과 스타일 우선순위
- Composition (스타일 병합)
- 중첩 선택자
- 미디어쿼리
- Global Styles (글로벌 스타일)
- ThemeProvider & theme.js 사용법
- style 공유 방법
- 동적 스타일 → style prop 사용
- Keyframes (애니메이션)
- 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({ ... }) 안에 속성별로 브레이크포인트별 값 배열 작성
- 화면 크기에 따라 fontSize와 padding 자동 적용
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주차 끝!
'3주차' 카테고리의 다른 글
styled-components와 @emotion/styled (0) | 2025.05.02 |
---|---|
상태 관리 (0) | 2025.05.02 |
React 의 클로저 (0) | 2025.05.02 |
React 함수 및 컴포넌트 네이밍 규칙 — 개념부터 기업 실전까지 (0) | 2025.05.02 |
Event loop로 알아보는 setTimeout과 setInterval의 정확성 (0) | 2025.05.02 |