리액트 스타일링 비교: CSS 모듈 vs styled-components
CSS 모듈과 styled-components는 리액트에서 가장 많이 쓰이는 두 가지 스타일링 방식입니다. 이 글에서는 설치부터 기본 사용법, 유지보수/성능/테마링까지 비교하고, 동일 UI를 두 방식으로 구현하는 실습 예제를 제공합니다.
1) 개념 한눈에 보기
| 항목 | CSS 모듈 | styled-components |
|---|---|---|
| 핵심 아이디어 | 파일 단위 클래스 이름 해싱으로 자동 스코프 | JS 안에서 styled.* 함수로 컴포넌트화 |
| 러닝 커브 | CSS 친숙 → 진입장벽 낮음 | 템플릿 리터럴 문법+런타임 개념 필요 |
| 동적 스타일 | 클래스 토글 / CSS 변수 활용 | props 기반 조건부 스타일 아주 쉬움 |
| 테마(Design System) | CSS 변수/전역 토큰 | ThemeProvider로 컨텍스트 기반 |
| 성능/번들 | 빌드 타임 처리(빠름, 오버헤드 적음) | 런타임 스타일 생성(최적화 옵션 제공) |
| 도구 연동 | PostCSS/Autoprefixer/전처리기 용이 | 자동 벤더 프리픽스, 타입 가드 등 생태계 풍부 |
2) 설치
Create React App(CRA)·Vite 등 대부분의 툴체인은 CSS 모듈을 기본 지원합니다. .module.css 네이밍만 맞추면 됩니다.
# styled-components 설치
npm i styled-components
# 타입스크립트면(선택)
npm i -D @types/styled-components
3) 기본 사용법
3-1) CSS 모듈
/* src/styles/Button.module.css */
.btn {
padding: 8px 12px;
border-radius: 8px;
font-weight: 600;
border: 1px solid #d0d7de;
background: #f6f8fa;
cursor: pointer;
}
.primary {
color: #fff;
background: #0969da;
border-color: #0969da;
}
.btn:disabled {
opacity: .6;
cursor: not-allowed;
}// src/components/Button.module.jsx
import React from 'react';
import styles from '../styles/Button.module.css';
export default function Button({ children, variant = 'default', ...props }) {
const cls = [styles.btn, variant === 'primary' && styles.primary].filter(Boolean).join(' ');
return <button className={cls} {...props}>{children}</button>;
}// src/examples/ButtonModuleDemo.jsx
import React from 'react';
import Button from '../components/Button.module.jsx';
export default function ButtonModuleDemo() {
return (
<div style={{ display: 'flex', gap: 8 }}>
<Button>Default</Button>
<Button variant="primary">Primary</Button>
<Button disabled>Disabled</Button>
</div>
);
}▼ 실행 화면 설명: Default/Primary/Disabled 버튼이 가로로 나란히 보이고, Primary는 파란 배경/흰 글씨로 표시됩니다. Disabled는 흐리게 보이며 클릭할 수 없습니다.

3-2) styled-components
// src/components/SButton.jsx
import styled from 'styled-components';
export const SButton = styled.button`
padding: 8px 12px;
border-radius: 8px;
font-weight: 600;
border: 1px solid #d0d7de;
background: #f6f8fa;
cursor: pointer;
${(p) => p.$primary && `
color: #fff;
background: #0969da;
border-color: #0969da;
`}
&:disabled { opacity: .6; cursor: not-allowed; }
`;// src/examples/SButtonDemo.jsx
import React from 'react';
import { SButton } from '../components/SButton'; // 이름 export 임포트
export default function SButtonDemo() {
return (
<div style={{ display: 'flex', gap: 8 }}>
<SButton>Default</SButton>
<SButton $primary>Primary</SButton>
<SButton disabled>Disabled</SButton>
</div>
);
}▼ 실행 화면 설명: Default/Primary/Disabled 버튼이 가로로 나란히 보이고, Primary는 파란 배경/흰 글씨로 표시됩니다. Disabled는 흐리게 보이며 클릭할 수 없습니다.

4) 성능·번들·SSR 관점
- CSS 모듈: 빌드 타임에 클래스가 해싱되어 런타임 오버헤드가 적고 캐싱이 유리합니다. 대규모 리스트 렌더링에서도 안정적입니다.
- styled-components: 런타임에서 스타일을 생성/주입합니다. 조건부 스타일/테마링이 매우 편리하지만, 매우 많은 변형을 렌더링하는 화면에서는 성능 예산을 고려해야 합니다. (바벨 플러그인/스타일 캐싱,
asprop 최소화 등 최적화 권장) - SSR: CSS 모듈은 일반적으로 문제 없고, styled-components는
ServerStyleSheet로 스타일 수집 후 HTML에 주입하는 패턴을 사용합니다.
5) 무엇을 선택할까? (실무 가이드)
- 컴포넌트/페이지 수가 많고 성능 예산이 빡빡하다면: CSS 모듈 + 전역 토큰(CSS 변수) 권장
- 테마/변형/조건부 스타일이 매우 빈번하다면: styled-components + ThemeProvider가 개발 효율이 높음
- 혼용 전략도 가능: 기본은 CSS 모듈로 두고, 테마·변형이 많은 디자인 시스템 컴포넌트만 styled-components로 제작
요약
- CSS 모듈: 단순/빠름/도구호환 우수
- styled-components: 동적 스타일/테마 최강, 러닝 커브·런타임 오버헤드 고려
- 프로젝트 성격에 따라 혼용이 가장 실용적
참고 링크
- CSS Modules 공식: https://github.com/css-modules/css-modules
- CRA의 CSS 모듈 가이드: https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/
- styled-components 문서: https://styled-components.com/docs
- styled-components SSR 가이드: https://styled-components.com/docs/advanced#server-side-rendering
함께 보면 좋은 게시글
- 리액트 JSX 문법 규칙: 인라인 스타일링·className·닫는 태그·주석
- React 이벤트 처리 방법: onClick·onChange·onSubmit 예제
- 리액트 useMemo 제대로 쓰기: 언제 이득이고 언제 오버엔지니어링일까
- 리액트 useCallback 제대로 쓰기: 참조 안정화로 자식 리렌더 줄이기
- 리액트 useEffect 사용법: 의존성 배열과 생명주기 이해
이 글이 도움이 되셨다면 공유 부탁 드립니다.



