🎯 목표
프로젝트 스타일 시스템이 emotion에서 css module로 변경되었습니다. 리소스를 잃는 건 아쉽지만 좋은 선택이라고
생각합니다. nextjs에서는 SSR을 기반으로 두고 있습니다. 런타임에 실행되는 CSS-IN-JS보단 빌드 과정에서 실행되는 일반 css 시스템이 하이드레이션 오류를 미연에 방지하기 좋기 때문입니다.
css module 정의를 알아본 후 clsx 라이브러리를 사용해 다중 클래스를 설정해보겠습니다..
📃진행 순서
- css-module
- 다중 클래스 설정하기
- css module 시스템의 단점
- clsx 라이브러리 활용
💻CSS module
리액트 프로젝트의 규모가 커질수록 외부 css를 불러들여 사용하는 것은 스타일 중복과 유지보수의 어려움을 줄 수 있습니다. 그래서 고안된 CSS module 시스템은 스타일이 적용될 때, 고유한 클래스명으로 변환해주는 기술입니다. 해당 시스템을 통해 CSS 충돌을 미연에 방지할 수 있습니다.
// use
(name).module.css
사용 시에는 객체 키 형태로 스타일을 사용할 수 있습니다.
import styles from './(name).module.css'
<div className={styles.tab}>
여기서 의문이 생길 수 있습니다. 각각 import를 다르게 한다면 module이라는 단어를 붙이지 않아도 되지 않을까?
하지만 module을 기입해야 리액트 컴포넌트에서 파일 경로, 해쉬값 등을 참조해 컴파일링합니다. 이때 각 컴포넌트 별로 고유한 네임 스페이스가 할당되어 분리된 스타일을 보장받습니다.

다중 클래스 설정하기
css 모듈 시스템에서 클래스를 여러 개 설정하려면 ES6 부터 지원하는 템플릿 리터럴 문법과 자바스크립트 join() 문법을 사용할 수 있습니다.
템플릿 리터럴 문법은 ``(백틱)과 {}(중괄호)를 결합하여 '+'를 사용하지 않아도 자바스크립트 문법을 치환해줍니다.
<Tab className={`${styles.Tab} ${styles.h1}`}>
자바스크립트 join 문법은 배열의 모든 요소를 문자열로 한번에 취합해 줍니다.
<Tab className={[styles.wrapper, styles.h1].join('')}/>
CSS Module의 단점
동적으로 클래스를 설정해야할 때, boolean 값으로 설정한 변수 마저 클래스에 추가되어 가독성을 저하시킵니다.
해당 예제는 isHovered의 값에 따라 style.span의 적용 유무를 지정합니다.
import styles from './(name).module.css
export function basicTab(){
const isHovered = false;
return <button className={`${styles.button} ${styles.span && isHovered}`}>
}
개발자 도구를 통해 확인해보면 <button class="button span false"> false가 클래스 자체로 들어가는 모습을 보입니다.
이런 문제를 해결하기 위해 classnames, clsx 등의 라이브러리를 주로 사용합니다.
clsx 라이브러리 활용
// 설치
$ npm install --save clsx
여러 개 클래스 주기
function Div(){
return (
<div className={clsx(styles.Tab, styles.blue)}>
</div>
)
}
조건 부로 클래스 주기
왼쪽에는 적용할 클래스값, 오른쪽에는 : 을 기준으로 키 값으로 사용할 변수를 작성해주면 됩니다.
function Div(){
const default = true;
const ready = true;
return (
<div className={
clsx(styles.Tab, // 기본으로 들어감
[styles.blue] : default && ready,
[styles.padding] : true
)}>
</div>
)
}
배운것을 토대로 버튼 스타일링을 실전 예제로 만들어보겠습니다.
아래 예제는 type을 props로 전달받아 두번째 클래스명으로 전달합니다.
export function Button({ children, type = 'default' }) {
return <button className={clsx(styles.button, styles[type])}>{children}</button>;
}
여기서 더 발전해 다양한 클래스 명을 전달받고 type, onclick 속성들도 전달되게 해보겠습니다.
export function Button({ children, className = '', variant = 'default', ...props }) {
const extraClasses = className.split(' ').map((clsName) => styles[clsName]);
return (
<button className={clsx(styles.button, styles[variant], ...extraClasses)} {...props}>
{children}
</button>
);
}
button에 type 속성이 있다는걸 망각했습니다. 좀 더 범용적인 variant로 선언해주었습니다. 추가로 다양한 클래스를 받았을 시
styles[clsName]으로 변환시켜 뿌려주었습니다.
...props는 onclick, type 등 버튼에 추가적인 속성 설정에 사용됩니다.
return(
<Button className="hi theme" type="submit">
안녕
</Button>
)
사용할 때 위와 같이 props를 넘겨줍니다.
이번 파트도 쉽지 않았습니다. 고려할 점이 많다고 느낍니다. 템플릿 리터럴 문법등 기본 자바스크립트 문법에 대한 이해도 필요했고, 라이브러리 자체 문법도 숙지해야합니다.
유연한 컴포넌트 설계를 위해서는 요구사항에 대한 깊은 이해와 서비스 확장성을 고려하는 습관이 필요할 것 같습니다.
📙 참고 사이트
- https://www.tcpschool.com/react/react_styling_cssmodule
- https://velog.io/@sanghyeon/clsx-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A1%9C-className-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EC%89%BD%EA%B2%8C-%ED%95%98%EA%B8%B0
'react' 카테고리의 다른 글
Next js Eslint import 구문 규칙 삽입 (0) | 2025.05.11 |
---|---|
React 버튼 컴포넌트 만들어보기 (0) | 2025.05.10 |
Next js public VS src (1) | 2025.05.06 |
Next js svgr 라이브러리 유연하게 사용하기 (0) | 2025.05.05 |
Next js Multiple layout 설정 (페이지별 공통 레이아웃) (0) | 2025.05.03 |