본문 바로가기
C.W.K.
Stream
Lesson 01 of 08 · published

CSS Module로 컴포넌트 스타일 가두기

~18 min · CSS Modules, scoping, no conflicts

Level 0호기심
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

클래스 이름 충돌을 빌드가 막아 줘

CSS Module은 클래스 이름의 범위를 그 파일을 import한 컴포넌트로 제한해. 빌드가 이름을 고유한 해시로 바꾸므로 Card.module.css.button과 다른 파일의 .button이 충돌하지 않아. 실행 시점에는 평범한 CSS라서 JavaScript나 hook이 필요 없고 서버 컴포넌트에서도 그대로 작동해.

*.module.css가 경계를 선언해

파일을 Card.module.css처럼 이름 짓고 기본 객체로 import한 뒤 className={styles.button}으로 사용해. 파일 안에는 일반 CSS 규칙을 쓰며, composes:로 다른 로컬 클래스를 조합할 수도 있어.

Tailwind와 경쟁 관계만은 아니야

Tailwind가 화면 대부분의 간격과 색을 빠르게 표현해도, 정교한 내부 상태, keyframe 애니메이션, 가상 요소가 많은 컴포넌트에서는 CSS Module이 더 읽기 쉬울 수 있어. 한 프로젝트에서 공통 utility는 Tailwind로, 캡슐화된 복잡한 규칙은 Module로 나눠도 돼.

범위는 해결하지만 설계 토큰은 대신하지 않아

클래스가 충돌하지 않는다고 색과 간격이 자동으로 일관되는 건 아니야. CSS 변수나 공통 토큰을 함께 써야 제품 전체의 시각 언어가 유지돼.

컴포넌트 내부에만 의미 있는 선택자와 animation은 Module에 두고, 색·간격·글꼴 같은 제품 토큰은 CSS 변수에서 공유해. 상태 variant가 많아지면 composes와 명시적 props로 가능한 조합을 제한해. DevTools에서 해시된 이름보다 최종 cascade와 사용하지 않는 규칙을 확인해.

클래스 충돌이 없다고 CSS가 자동으로 단순해지지는 않아. 깊은 descendant 선택자와 높은 specificity를 Module 안에 숨기면 재사용할 때 같은 cascade 세금을 내. 로컬 범위는 캡슐화 도구이지 복잡한 DOM 의존성을 정당화하는 면허가 아니야. 같은 .button 이름을 두 Module에 만들고 최종 class 충돌이 없는지 확인한 뒤 composes로 세 variant를 구성해. 서버 컴포넌트에서 사용했을 때 클라이언트 JavaScript가 늘지 않는지 보고, 키보드 focus와 disabled 상태가 cascade에 가려지지 않는지도 시험해.

Code

Button.module.css·css
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background: #2563eb;
  color: white;
  border: none;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.button:hover {
  background: #1d4ed8;
}

.primary {
  composes: button;
  font-weight: 600;
}

.danger {
  composes: button;
  background: #dc2626;
}

.danger:hover {
  background: #b91c1c;
}
CSS Module 사용하기·tsx
import styles from './Button.module.css';

export function PrimaryButton({ children }: { children: React.ReactNode }) {
  return <button className={styles.primary}>{children}</button>;
}

export function DangerButton({ children }: { children: React.ReactNode }) {
  return <button className={styles.danger}>{children}</button>;
}
// 빌드된 클래스 이름: 'Button_primary__a3b2c' — 항상 전역에서 고유.

External links

Exercise

CSS Module과 composes로 primary·secondary·danger 버튼을 만들어. 최종 클래스가 해시되고 다른 Module의 같은 이름과 충돌하지 않는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.