Search
Duplicate

React CSS module

React CSS 모듈 완전 가이드

목차

CSS 모듈이란?

CSS 모듈(CSS Modules)은 CSS 클래스명을 로컬 스코프로 제한하여 클래스명 충돌을 방지하는 기술입니다. React에서 컴포넌트별로 독립적인 스타일을 적용할 때 매우 유용합니다.

일반 CSS vs CSS 모듈

일반 CSS의 문제점:
/* styles.css */ .button { background-color: blue; color: white; }
CSS
복사
→ 전역 스코프에서 .button 클래스가 모든 컴포넌트에 영향을 미침
CSS 모듈의 해결책:
/* Button.module.css */ .button { background-color: blue; color: white; }
CSS
복사
→ 클래스명이 Button_button__hash123 형태로 변환되어 충돌 방지

CSS 모듈의 장점

1. 스코프 격리

컴포넌트별로 독립적인 CSS 스코프 제공
클래스명 충돌 완전 방지

2. 유지보수성 향상

스타일과 컴포넌트의 1:1 매핑
코드 리팩토링 시 안전성 보장

3. 명시적 의존성

import 문을 통한 명확한 스타일 의존성 표현
사용하지 않는 스타일 쉽게 식별 가능

4. 빌드 타임 최적화

사용되지 않는 CSS 자동 제거
클래스명 최적화로 번들 크기 감소

기본 사용법

1. CSS 모듈 파일 생성

파일명 규칙: ComponentName.module.css
/* Button.module.css */ .primary { background-color: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; } .secondary { background-color: #6c757d; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; } .large { padding: 15px 30px; font-size: 18px; } .small { padding: 5px 10px; font-size: 12px; }
CSS
복사

2. React 컴포넌트에서 사용

// Button.jsx import React from 'react'; import styles from './Button.module.css'; const Button = ({ variant = 'primary', size = 'medium', children, ...props }) => { return ( <button className={`${styles[variant]} ${styles[size]}`} {...props} > {children} </button> ); }; export default Button;
JavaScript
복사

3. 컴포넌트 사용 예제

// App.jsx import React from 'react'; import Button from './components/Button'; function App() { return ( <div> <Button variant="primary" size="large"> Primary Large Button </Button> <Button variant="secondary" size="small"> Secondary Small Button </Button> </div> ); } export default App;
JavaScript
복사

고급 사용법

1. 동적 클래스명

// Card.jsx import styles from './Card.module.css'; const Card = ({ isActive, hasError, children }) => { const cardClasses = [ styles.card, isActive && styles.active, hasError && styles.error ].filter(Boolean).join(' '); return ( <div className={cardClasses}> {children} </div> ); };
JavaScript
복사

2. classnames 라이브러리 활용

먼저 설치:
npm install classnames
Shell
복사
사용 예제:
import classNames from 'classnames/bind'; import styles from './Card.module.css'; const cx = classNames.bind(styles); const Card = ({ isActive, hasError, size }) => { return ( <div className={cx('card', { active: isActive, error: hasError, [`size-${size}`]: size })}> Content </div> ); };
JavaScript
복사

3. CSS 변수와 함께 사용

/* Theme.module.css */ .container { --primary-color: #007bff; --secondary-color: #6c757d; --spacing: 1rem; } .button { background-color: var(--primary-color); margin: var(--spacing); } .darkTheme { --primary-color: #0056b3; --secondary-color: #545b62; }
CSS
복사
import styles from './Theme.module.css'; const ThemedComponent = ({ isDark }) => { return ( <div className={`${styles.container} ${isDark ? styles.darkTheme : ''}`}> <button className={styles.button}>Themed Button</button> </div> ); };
JavaScript
복사

4. 중첩 선택자와 의사 클래스

/* Navigation.module.css */ .nav { display: flex; list-style: none; padding: 0; } .navItem { margin-right: 1rem; } .navItem:hover { background-color: #f8f9fa; } .navItem:last-child { margin-right: 0; } .navLink { text-decoration: none; color: #333; padding: 0.5rem 1rem; border-radius: 4px; transition: all 0.2s ease; } .navLink:hover { background-color: #e9ecef; } .navLink.active { background-color: #007bff; color: white; }
CSS
복사

실전 예제

완전한 카드 컴포넌트 구현

/* Card.module.css */ .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); overflow: hidden; transition: all 0.3s ease; } .card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateY(-2px); } .header { padding: 1.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .title { margin: 0; font-size: 1.25rem; font-weight: 600; } .subtitle { margin: 0.5rem 0 0 0; opacity: 0.9; font-size: 0.875rem; } .body { padding: 1.5rem; } .footer { padding: 1rem 1.5rem; background: #f8f9fa; border-top: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; } .actions { display: flex; gap: 0.5rem; } .button { padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.875rem; transition: all 0.2s ease; } .buttonPrimary { background: #007bff; color: white; } .buttonPrimary:hover { background: #0056b3; } .buttonSecondary { background: transparent; color: #6c757d; border: 1px solid #dee2e6; } .buttonSecondary:hover { background: #e9ecef; } /* 반응형 디자인 */ @media (max-width: 768px) { .card { margin: 0.5rem; } .header, .body, .footer { padding: 1rem; } .footer { flex-direction: column; gap: 1rem; align-items: stretch; } .actions { justify-content: center; } }
CSS
복사
// Card.jsx import React from 'react'; import styles from './Card.module.css'; const Card = ({ title, subtitle, children, onPrimaryAction, onSecondaryAction, primaryActionText = "확인", secondaryActionText = "취소" }) => { return ( <div className={styles.card}> {(title || subtitle) && ( <div className={styles.header}> {title && <h3 className={styles.title}>{title}</h3>} {subtitle && <p className={styles.subtitle}>{subtitle}</p>} </div> )} <div className={styles.body}> {children} </div> {(onPrimaryAction || onSecondaryAction) && ( <div className={styles.footer}> <div className={styles.actions}> {onSecondaryAction && ( <button className={`${styles.button} ${styles.buttonSecondary}`} onClick={onSecondaryAction} > {secondaryActionText} </button> )} {onPrimaryAction && ( <button className={`${styles.button} ${styles.buttonPrimary}`} onClick={onPrimaryAction} > {primaryActionText} </button> )} </div> </div> )} </div> ); }; export default Card;
JavaScript
복사

사용 예제

// App.jsx import React from 'react'; import Card from './components/Card'; function App() { const handleSave = () => { console.log('저장됨'); }; const handleCancel = () => { console.log('취소됨'); }; return ( <div style={{ padding: '2rem', background: '#f5f5f5', minHeight: '100vh' }}> <Card title="사용자 정보" subtitle="프로필 정보를 확인하고 수정하세요" onPrimaryAction={handleSave} onSecondaryAction={handleCancel} primaryActionText="저장" secondaryActionText="취소" > <p>이름: 홍길동</p> <p>이메일: hong@example.com</p> <p>가입일: 2024-01-15</p> </Card> </div> ); } export default App;
JavaScript
복사

트러블슈팅

1. 클래스명이 적용되지 않는 경우

문제: 스타일이 적용되지 않음
// ❌ 잘못된 방법 <div className="button">버튼</div>
JavaScript
복사
해결:
// ✅ 올바른 방법 <div className={styles.button}>버튼</div>
JavaScript
복사

2. 하이픈이 포함된 클래스명

CSS:
.nav-item { color: blue; }
CSS
복사
사용:
// ❌ 에러 발생 <div className={styles.nav-item}> // ✅ 대괄호 표기법 사용 <div className={styles['nav-item']}> // ✅ 또는 카멜케이스로 변경 .navItem { color: blue; } <div className={styles.navItem}>
JavaScript
복사

3. 전역 스타일과 모듈 스타일 충돌

/* styles.module.css */ :global(.global-class) { /* 전역 스타일 */ } .local-class { /* 로컬 스타일 */ } /* 특정 전역 클래스 내부의 로컬 스타일 */ :global(.container) .localContent { color: red; }
CSS
복사

4. 빌드 환경 문제

Create React App이 아닌 경우 webpack 설정:
// webpack.config.js module.exports = { module: { rules: [ { test: /\.module\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: { localIdentName: '[name]__[local]___[hash:base64:5]' } } } ] } ] } };
JavaScript
복사

대안 및 비교

1. CSS Modules vs Styled Components

특징
CSS Modules
Styled Components
러닝 커브
낮음
중간
번들 크기
작음
런타임 성능
좋음
보통
개발자 도구
보통
좋음
테마 지원
수동
내장

2. CSS Modules vs CSS-in-JS

// CSS Modules import styles from './Button.module.css'; const Button = () => <button className={styles.primary}>버튼</button>; // CSS-in-JS (Emotion) import { css } from '@emotion/react'; const Button = () => ( <button css={css`background: blue; color: white;`}> 버튼 </button> );
JavaScript
복사

3. 언제 CSS Modules를 사용해야 할까?

CSS Modules 권장 상황:
기존 CSS에 익숙한 팀
빠른 개발 속도가 필요한 경우
번들 크기 최적화가 중요한 경우
정적 스타일이 주를 이루는 경우
다른 방법 고려 상황:
동적 스타일링이 많이 필요한 경우 → Styled Components
복잡한 테마 시스템이 필요한 경우 → CSS-in-JS
매우 큰 규모의 프로젝트 → CSS-in-JS + 디자인 시스템

마무리

CSS Modules는 React에서 컴포넌트 기반 스타일링을 구현하는 강력하고 효율적인 방법입니다. 기존 CSS 지식을 그대로 활용하면서도 모던 웹 개발의 요구사항을 충족할 수 있어, 많은 프로젝트에서 채택되고 있습니다.
핵심은 컴포넌트와 스타일의 1:1 매핑을 통해 유지보수하기 쉬운 코드를 작성하는 것입니다. 이 가이드의 예제들을 참고하여 프로젝트에 적용해보시기 바랍니다.