최근 Tailwind CSS v4를 사용한 개인 프로젝트에서, 커스텀 태그를 활용한 동적 스타일 적용을 하던 도중에 문제를 발견했습니다. 이번 글에서는 해당 문제 상황과 해결을 위해 시도했던 방법들, 그리고 최종적으로 Tailwind CSS v4의 신규 기능인 @source inline() 디렉티브를 활용하여 문제를 해결한 경험을 공유해보고자 글을 적습니다. 또한 TailwindCSS v4 환경에서 global.css 만으로 구성하는 방법에 대해서도 간략하게 다뤄보도록 하겠습니다.
문제 상황 : 커스텀 태그로 동적 스타일 적용 실패
개인 프로젝트에서 UI 텍스트 내에 <sb>, <b-sb> 등의 커스텀 태그들을 정의하여, 이를 React 컴포넌트에서 특정 스타일로 렌더링하는 방식을 구현하고 있었습니다.
개발자 도구를 보면 text-blue-900 가 적용이 되어있는데 글자색은 기본 텍스트 색상으로 되어있습니다.
예를 들어 <sb> 태그는 semibold의 준말로 이 태그로 둘러쌓인 텍스트의 경우에는 text-blue-900, font-semibold, text-[19px] 클래스로 표시되도록 구현하고 싶었습니다. 이러한 태그들은 커스텀 훅인 useFormattedText() 를 통해서 파싱되어 React 요소로 변환되며, 예를 들어 아래와 같이 동작하도록 했습니다.
[실제 코드]
{
tag: '<b-sb>',
end: '</b-sb>',
render: (children: React.ReactNode, key: string) => (
<span key={key} className="font-semibold text-blue-900 text-[19px]">
{children}
</span>
),
},
[예시]
const text = '알림. <sb>변경하려는 텍스트</sb>를 확인해주세요.'
const formatted = useFormattedText(text)
//결과: "알림. " + <span className="text-blue-900 font-semibold text-[19px]">변경하려는 텍스트</span> + "를 확인해주세요."
위와 같이 <sb>태그로 감싼 "변경하려는 텍스트" 부분은 최종적으로 <span class="text-blue-900 font-semibold text-[19px]>변경하려는 텍스트</span> 형태로 렌더링되어 동적으로 TailwindCSS 유틸리티 클래스가 적용 되는 원리입니다.
하지만 문제가 발생했습니다. Tailwind 설정은 별도의 tailwind.config.ts 파일 없이 global.css 파일에 @tailwindcss;만 포함한 CSS 기반 구성을 사용하고 있었습니다. v4로 업그레이드 이후에 <sb> 등을 통해 렌더링 요소에 text-blue-900 등의 클래스가 DOM에 나타나긴 하지만, 실제 스타일이 적용되지 않는 현상이 발견되었습니다. 개발자 도구로 확인해보면 <span class="text-blue-900 font-semibold text-[19px]>처럼 클래스는 존재하지만 해당 클래스에 대한 CSS 정의가 누락되어 글자 색상과 크기가 변하지 않았습니다.
원인 분석: Tailwind의 정적 클래스 추출과 동적 클래시 누락
Tailwind CSS는 정적 분석을 통해 사용된 클래스만을 추출하여 최종 CSS를 생성하는 방식으로 동작합니다. 빌드 시점에 소스 코드에서 찾은 클래스들만 CSS를 생성하고 그 외의 것은 포함하지 않기 떄문에, 런타임에 동적으로 만들어지는 클래스는 Tailwind가 인지하지 못하면 CSS에 포함되지 않습니다.
즉, 구현에서 <sb> 커스텀 태그를 런타임에 파싱하여 text-blue-900 클래스명을 적용했는데, Tailwind 빌드 단계에서 이러한 클래스명이 미리 명시적으로 드러나 있지 않으므로 해당 스타일을 생성하지 않았던 것입니다. Tailwnd JIT는 템플릿이나 코드에 하드코딩된 클래스만 인식하기 때문에, 'text-' + color 와 같이 문자열 연결로 생성되거나 런타임에 결정되는 클래스명은 감지되지 않아 스타일이 누락됩니다. 결과적으로 DOM에는 클래스가 있지만 그에 상응하는 CSS 규칙이 없어 스타일이 미적용되는 문제가 발생한 것입니다.
시도 했던 방법들(실패 사례)
이 문제를 해결하기 위해 몇 가지 방법을 시도했습니다. 그러나 완벽한 해결책이 되지 못했습니다.
Tailwind safelist 설정
Tailwind v3까지는 tailwind.config.ts 에 safelist 옵션을 사용하여 빌드 타임에 감지되지 않는 클래스들을 화이트리스트에 추가할 수 있었습니다.
실제로 Tailwind v3에서는 safelist를 통해 동적 클래스도 강제로 포함시키는 전략을 사용할 수 있엇지만, TailwindCSS v4에서는 safelist 옵션이 제거되었습니다. v4부터는 JS 기반 구성의 safelist를 더이상 지원하지 않기 때문에, 이 방법은 사용할 수 없었습니다.
더미 JSX 요소에 클래스 작성
동적으로 생성되는 클래스들을 Tailwind가 인식하도록 하기 위해, 컴포넌트에 실제로는 화면에 보이지 않는 더미 요소를 추가하고 거기에 필요한 클래스를 모두 나열하는 방법도 고려했습니다.
예를 들어 <div className="hidden text-red-600 text-blue-900 font-semibold font-medium hover:text-main-500" /> 처럼 숨긴 요소에 모든 필요한 클래스를 포함시키면 Tailwind가 이를 감지하여 CSS를 생성합니다. 이 방법은 동작은 하지만, 유지보수가 어렵고 비효율적이였습니다. 또 필요한 클래스가 늘어날 때마다 더미 요소를 추가해야 했고, 코드를 지저분하게 만들기 때문에 장기적으로 적절한 해결책이 아니었습니다.
최종 해결 방법
문제를 해결한 방법은 Tailwind CSS v4.1에서 새롭게 도입된 @source inline() CSS 디렉티브였습니다.Tailwind v4에서는 설정 방식을 CSS 우선 접근법으로 개편하면서, safelist 기능을 대체할 수단으로 @source inline()을 제공하고 있었습니다.
이 디렉티브를 사용하면 Tailwind의 정적 분석에 포착되지 않는 클래스들도 수동으로 지정하여 최종 빌드된 CSS에 포함시킬 수 있습니다.
공식 문서에 따르면 @souce inline()은 이전 버전들의 safelist 옵션과 동일한 역할을 수행하며, 다만 JS config 대신 CSS 파일 내에서 이를 선언하는 방식입니다.
@import tailwindcss
@source inline("text-blue-900 text-[19px]")
위와 같이 @source inline(" ... ") 내부에 문제된 클래스들을 문자열로 나열했습니다. 문자열에 적용하려는 스타일, 그리고 호버 색상 클래스까지 포함했습니다. Tailwind는 빌드 시 이 inline 지시문을 해석하여 해당 클래스들의 CSS를 반드시 생성하게 됩니다.
결과적으로, 위 설정을 추가한 후 다시 빌드해보니 요소에 스타일이 정상적으로 적용되었습니다. 개발자 도구에서 확인해보면 이제 text-blue-900 클래스에 대응하는 CSS가 생성되어 포함된 것을 볼 수 있었습니다.
추가 팁
Tailwind CSS v4.1 의 @souce inline()은 내부적으로 brace expansion 문법을 지원하여 여러 클래스를 효율적으로 생성할 수도 있습니다. 예를 들어 공식 예시로
@source inline('{hover:,}bg-red-{50,{100..900..100},950}');
이렇게 작성하면, hover 변형을 포함한 모든 클래스를 한 번에 생성할 수 있습니다. 예시는 비교적 몇 개의 클래스만 필요하므로 공백으로 구분해 나열했지만, 필요에 따라 brace expansion을 활용하면 규칙적인 패턴의 클래스를 간결하게 포함시킬 수도 있습니다.
마치며
이번 경험을 통해 동적 클래스가 Tailwind CSS에서 누락될 수 있다는 점과, 프레임워크 업그레이드 시 새로운 기능들을 적극 활용하는 것이 얼마나 중요한지 깨달았습니다. 정적 분석 기반으로 동작하는 Tailwind에서는 콘텐츠에 직접 드러나지 않는 클래스는 기본적으로 제외되므로, 저처럼 런타임에 클래스를 생성하는 경우 반드시 Tailwind에 해당 클래스를 알려줘야 합니다.
정리
- 동적 클래스 적용이 안된다면, 우선 tailwind가 그 클래스를 인식하고 CSS를 생성했는지 의심, 정적이지 않은 클래스명은 빌드 타임에 누락되어 스타일이 빠질 수 있음.
- Tailwind CSS v4에서는 더 이상 tailwind.config.js의 safelist 옵션을 지원하지 않으므로, v3 방식으로는 해결이 불가능. 대신 @source inline() 지시문을 활용해 필요한 클래스를 CSS에 직접 명시하면 Tailwind가 이를 포착하여 스타일을 생성함.
- Next.js 환경에서 Tailwind를 global.cs로 설정할 경우, 해당 CSS 파일 내에서 Tailwind 관련 구성을 모두 처리할 수 있음. content 경로 지정은 자동화되었고, safelist도 CSS 디렉티브로 대체되었으므로, config 파일 없이도 대부분의 기능을 사용할 수 있음.
참고자료
https://tailwindcss.com/blog/tailwindcss-v4-1
Tailwind CSS v4.1: Text shadows, masks, and tons more
I wasn't sure it would ever happen but we did it — we released a version of Tailwind CSS that includes text shadow utilities. Tailwind CSS v4.1 is here and it's packed with improvements that will help you (or your LLM, you coward) build even better inter
tailwindcss.com
https://tailwindcss.com/docs/upgrade-guide
Upgrade guide - Getting started
Upgrading your Tailwind CSS projects from v3 to v4.
tailwindcss.com
https://tailkits.com/blog/tailwind-dynamic-classes/
Tailwind Dynamic Classes Explained | Tailkits
This post details the challenges of dynamic classes in Tailwind CSS and offers actionable techniques for reliable, maintainable styling.
tailkits.com
'프론트엔드 기록' 카테고리의 다른 글
달라진 TailwindCSS v4 커스텀하기 (0) | 2025.04.11 |
---|---|
Cursor와 Model Context Protocol(MCP)로 Figma 디자인 자동화 경험하기 (0) | 2025.04.01 |