
'런타임 비용을 줄여보자!' 며 패기있게 시작한 스타일링 이원화 전략은 생각보다 더 다뤄야할 요소들이 깊었습니다..
스타일링 이원화 전략 정리 글 ( ▶이동 )
먼저 스타일의 우선순위 선정이 필요했습니다.
이번 글은 스타일링 우선순위 선정을 주제로 아래 목차 순으로 진행해보겠습니다~
목차
1. 문제 상황 ( ▶이동 )
2. CSS Cascade Layer 개념 ( ▶이동 )
3. Tailwind layer 개념 ( ▶이동 )
4. 해결 전략 : Emotion Unlayered 설정 ( ▶이동 )
5. 환경별 세팅 ( ▶이동 )
6. 활용하기 위해 알아야 하는 개념들 ( ▶이동 )
Tailwind CSS(유틸리티 클래스)와 Emotion(CSS-in-JS)를 함께 사용하면 아래와 같은 장점들을 가져갈 수 있다.
Tailwind : 빠른 레이아웃 구현, 간격, 반응형 처리Emotion : 컴포넌트 단위 스타일 캡슐화, 디자인 토큰 기반 스타일링, 상태 기반 동적 스타일링하지만, 두 라이브러리를 함께 사용할 때 CSS 우선 순위 충돌 문제가 발생할 수 있다.
발생 원인과 CSS Cascade Layer를 활용한 해결 방법을 정리해본다.
CSS는 아래와 같은 세 가지 기준으로 우선 순위를 정한다.
#id > .class > tag)!important : 모든 규칙을 무시하고 적용먼저 각각 생성 스타일을 살펴보겠다.
Emotion 생성 스타일
.css-abc123 { padding: 16px }
Tailwind 생성 스타일
.p-4 { padding: 1rem }
두 스타일 모드 클래스 선택자(.로 시작) 1개이므로 Specificity가 동일하다.
이 경우 Source Order(선언 순서)로 우선순위가 결정된다.
문제는 이 경우에 발생한다.
빌드 환경 A : Tailwind가 나중에 삽입 → Tailwind 승
빌드 환경 B : Emotion이 나중에 삽입 → Emotion 승
동일한 출력물도 빌드 환경, 번들러 설정, 청크 분할 방식에 따라 CSS 삽입 순서가 달라지므로 결과를 예측할 수 없다.
참고
- 빌드 환경 :
React,Next.js등- 번들러 설정 :
Vite,Webpack,Turbopack등- 청크 분할이란? : 하나의 큰 번들을 여러 개의 작은 파일(chunk)로 나누는 것
CSS Cascading and Inheritance Level 5 스펙에서 도입된 우선순위 그룹화 메커니즘이다.
@layer base, components, utilities;
@layer base {
/* 가장 낮은 우선 순위 */
}
@layer utilities {
/* 가장 높은 우선 순위 */
}
@layer A, B, C에서 C가 우선 순위가 가장 높다. → Source Order
상위 layer의 .class가 하위 layer의 #id를 이김 (#id > .class > tag )
@layer 밖의 스타일이 모든 layer보다 우선

layer는 다음과 같이 4개의 layer로 구성된다.
theme : CSS 변수, 테마 토큰base : Preflight(브라우저 초기화)components : @apply 기반 컴포넌트 스타일utilities : 유틸리티 클래스(p-4, mt-2 등)Tailwind에 내장된 브라우저 초기화 스타일이다.
modern-normalize를 기반으로 하며, base layer에 포함된다.
초기화 내용이 좀 익숙하였다.
그렇다.
reset.css와 중복된다.
Tailwind v4에서는 간결한 import 문법을 사용한다
@layer theme, base, components, utilities;
@import 'tailwindcss';
이 한 줄로 theme, preflight, utilities가 모두 포함된다.
만약 개별 import가 필요한 경우라면 아래와 같이 진행한다.
@layer theme, base, components, utilities
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/preflight.css' layer(base);
@import 'tailwindcss/utilities.css' layer(utilities)
Emotion에서 @layer를 직접 지원하는 방법을 검토했다.(with 클로드)
Emotion v11이 현재 @layer를 네이티브로 지원하지 않는다.
커스텀 플러그인이나 후처리 방식은 복잡도가 높고, 안정성이 떨어진다.
결론
Emotion을 Unlayered로 두는 전략이 가장 실용적이다.

CSS 스펙 규칙상
Unlayered 스타일은 모든 Layered 스타일보다 우선한다.
Tailwind로 레이아웃 → Emotion으로 컴포넌트 스타일링 순서가 자연스럽게 보장된다.

Tailwind Preflight가 브라우저 초기화를 담당한다.
그러므로 기존 reset.css는 삭제한다.
중복 선언을 방지하고 유지보수를 단순화 할 수 있다.
현재 전략은 Emotion이 Tailwind utilities보다 우선순위가 높다.
만약 특정 상황에서 Tailwind 유틸리티로 Emotion 스타일을 덮어써야 한다면 ! 접두사를 이용한다.

참고
!접두사는 해당 유틸리티에!important를 추가한다.
남용은 하지말고..꼭 필요한 경우에만 사용하자..!
현재 나는 동시에 참여 중인 React, Next.js에서 모두 이원화 전략을 사용하고 있다.

맞다..그래서 2번의 세팅을 진행했다..( 주여.. )

main.tsx
globals.css import만 하면 끝!!
Next.js에서 App Router에서 Emotion을 사용할 때는 SSR 대응을 위한 추가 설정이 필요하다.
app/globals.css
React와 동일하게 globals.css를 설정한다.
app/EmotionCacheProvider.tsx
app/layout.tsx
@layer 선언 위치 : CSS 파일 최상단에 위치하는 지빌드 테스트 : dev/prod 환경에서 동일하게 동작하는지브라우저 확인 : 개발자 도구에서 Computed 탭에서 적용된 스타일 확인#id > .class > tag)@layer 사용 시 Layer 우선순위가 Specificity보다 먼저 적용됨@layer 밖에 선언된 스타일@layer 안에 선언된 스타일우선 순위 : Unlayered > Layered
Unlayered > Layered ( Layer > Source Order > Specificity(#id > .class > tag) )
@apply 기반 컴포넌트우선순위
theme → base → components → utilities
@import "tailwindcss"; 한줄로 전체 importimport 시 layer( )함수로 맵핑여기서 주의해야할 점은,
위에서 언급한 Preflight는 layer 개념으로
"tailwindcss"에 내장되어 있다.
그래서@layer를 사용할 경우 무조건@layer를 최상단에 선언한 뒤,@import "tailwindcss";를 위치시켜야 한다.
@layer선언 전에@import "tailwindcss";를 진행한다면,이후@layer선언이 기존 구조를 덮어쓰거나 무시될 수 있다.
@layer 선언 : (theme→ base → components → utilities 순으로 렌더) 스타일시트 처리 시점에서 layer 우선순위 구조 확정@import "tailwindcss"; : 이미 선언된 layer 안에 스타일 배치@config(optional) : Tailwind 설정(tailwind.config.ts) 설정 적용참고
Tailwind v4부터는tailwind.config.ts를 사용하지 않는다.
다만, 나는 스타일링 이원화에 따른 중앙 관리형 타이포시스템 사용을 위해 추가로 설정했다.
<style> 태그 동적 삽입<style>태그 삽입 위치prepend : true → head 최상단에 삽입 → 우선순위 낮아짐.@layer 사용 시 prepend 설정은 의미가 없음. layer 우선순위가 결정되기 때문에.useServerInsertedHTML로 SSR시 스타일 삽입CacheProvider로 클라이언트 스타일 관리Tailwind + Emotion 동일 속성 충돌, 빌드마다 출력되는 결과 다름Emotion을 Unlayered로 유지(최상위 우선순위)!접두사 사용globals.css 에 @layer 선언reset.css 삭제(tailwind preflight로 대체)
( 이렇게 또 이슈 상황에서 살아남았다..! )
내가 판 무덤같은 이원화 전략이지만, 이왕 선택한 김에 제대로 활용해보고 싶다.
언젠간 만날 클린 코드를 기다리며..글을 마칩니당~
참고 자료
Tailwind Preflight 공식 문서
MDN @layer
Emotion Server Side Rendering 공식 문서
CSS Cascading and Inheritance Level 5