Tailwind CSS

Tailwind CSS 클래스 적용 안 됨 원인과 해결 순서

2026.05.16·수정 2026.05.19·약 20분

Tailwind CSS 클래스가 적용되지 않을 때 확인할 것

Tailwind CSS를 쓰다 보면 className에는 분명히 클래스가 들어가 있는데 화면에는 스타일이 보이지 않는 경우가 있습니다. 이 문제는 단순 오타일 수도 있지만, CSS import 누락, 소스 감지 범위 문제, 동적 클래스 조합, 상태 variant 조건, 기존 CSS와의 충돌 때문에 생기기도 합니다. 이 글에서는 React와 Next.js 작업 중 Tailwind 스타일이 빠졌을 때 원인을 좁히는 순서를 정리합니다.

먼저 증상을 나눠서 보기

Tailwind CSS 클래스 적용 누락 원인을 CSS 로드, 파일 감지, 동적 클래스, 우선순위 순서로 진단하는 흐름

Tailwind CSS 클래스가 적용되지 않는다고 느낄 때는 바로 클래스를 바꾸기보다 증상부터 나누는 편이 낫습니다. 모든 유틸리티 클래스가 전부 먹지 않는 문제와, 특정 컴포넌트의 일부 클래스만 빠지는 문제는 원인이 다릅니다. 같은 “스타일이 안 먹는다”라는 표현으로 묶이지만, 실제로는 확인할 파일과 수정 방식이 달라집니다.

예를 들어 flex, px-4, text-sm처럼 기본적인 클래스까지 전부 적용되지 않는다면 Tailwind CSS가 앱에 제대로 연결되지 않았을 가능성이 큽니다. 이때는 전역 CSS import, 빌드 설정, 개발 서버 재시작을 먼저 봐야 합니다. 반대로 기본 클래스는 적용되는데 bg-blue-500 같은 색상만 빠지거나, 특정 조건에서만 색상이 사라진다면 동적 클래스 조합을 의심해야 합니다.

React나 Next.js 프로젝트에서는 이 문제가 더 자주 보입니다. 컴포넌트에 props를 넘기고, 그 값으로 className을 조합하는 일이 많기 때문입니다. 버튼의 색상, 배지의 상태, 카드의 크기처럼 값이 바뀌는 UI를 만들다 보면 bg-${color}-500 같은 코드를 작성하기 쉽습니다. 브라우저에는 class 속성이 찍히지만, Tailwind가 빌드 시점에 해당 클래스를 만들지 못하면 화면에는 아무 스타일도 보이지 않을 수 있습니다.

증상먼저 볼 위치가능성이 큰 원인
Tailwind 클래스가 거의 전부 적용되지 않음전역 CSS, 앱 진입점, 개발 서버CSS import 누락 또는 설정 미반영
특정 폴더의 컴포넌트만 적용되지 않음소스 감지 범위, 모노레포 경로Tailwind가 해당 파일을 스캔하지 못함
조건부 색상이나 크기만 적용되지 않음className 문자열 조합완성된 클래스명이 소스에 없음
hover, disabled 스타일만 안 보임DOM 상태와 variant 조건상태 조건이 실제로 충족되지 않음
개발자 도구에서 스타일이 취소선 처리됨CSS 우선순위, 기존 클래스다른 CSS 규칙이 Tailwind를 덮음

Tailwind가 클래스를 찾는 방식

Tailwind는 브라우저에서 렌더링된 DOM을 읽고 CSS를 만드는 도구가 아닙니다. 프로젝트의 소스 파일을 훑으면서 유틸리티 클래스처럼 보이는 문자열을 찾고, 발견한 클래스에 맞는 CSS를 생성합니다. 그래서 Tailwind 문제를 볼 때는 브라우저에 최종 class 속성이 찍혔는지만 보면 부족합니다. 소스 코드 안에 Tailwind가 감지할 수 있는 완성된 클래스 문자열이 있는지도 같이 봐야 합니다.

이 차이를 놓치면 디버깅이 길어집니다. React 입장에서는 className에 문자열이 정상적으로 들어갔으니 문제가 없어 보입니다. 하지만 Tailwind는 JavaScript 실행 결과를 예측해서 CSS를 만드는 방식이 아니기 때문에, 문자열 연결이나 템플릿 리터럴로 나중에 완성되는 클래스는 감지하지 못할 수 있습니다.

아래 코드는 브라우저에서는 bg-blue-600처럼 보일 수 있지만, Tailwind가 소스 파일을 스캔하는 시점에는 완성된 문자열이 없습니다.

function Button({ color }) {
  return <button className={`bg-${color}-600 text-white px-4 py-2`}>저장</button>;
}

이런 코드는 Tailwind 입장에서 bg-blue-600, bg-red-600, bg-gray-600 중 무엇이 필요한지 알 수 없습니다. 화면에 class 속성이 있어도 CSS 파일에 해당 규칙이 없으면 스타일은 적용되지 않습니다. 따라서 “DOM에는 클래스가 있는데 CSS가 없다”는 상태가 만들어집니다.

Tailwind는 완성된 문자열을 기준으로 CSS를 생성하므로, 조건에 따라 바뀌는 UI도 최종 클래스 후보를 코드 안에 직접 남겨야 합니다. 이 원칙만 잡아도 동적 className 문제의 대부분은 정리됩니다.

전역 CSS와 소스 감지 확인

가장 기본적인 확인 지점은 Tailwind CSS가 전역 CSS에 들어와 있는지입니다. Tailwind v4 기준으로는 보통 전역 CSS 파일에 @import "tailwindcss";를 작성합니다. Next.js App Router라면 보통 app/globals.css에 이 구문이 들어가고, 루트 레이아웃에서 해당 CSS를 import합니다. Vite 기반 React 프로젝트라면 src/index.css 또는 src/main.css를 만들고, main.jsxmain.tsx에서 불러오는 구조가 많습니다.

@import "tailwindcss";

여기서 자주 나오는 실수는 CSS 파일에는 Tailwind import가 있지만 앱 진입점에서 그 CSS를 불러오지 않는 경우입니다. CSS 파일만 존재하고 실제 번들에 포함되지 않으면 Tailwind 클래스는 화면에 반영되지 않습니다. 파일 내용과 import 위치를 같이 확인해야 합니다.

다음은 소스 감지 범위입니다. Tailwind v4는 자동 소스 감지를 제공하지만, 모든 상황을 자동으로 해결해주는 것은 아닙니다. 기본적으로 무시되는 위치나 외부 패키지, 모노레포의 공유 UI 패키지, 별도 폴더에 둔 컴포넌트처럼 Tailwind가 스캔하지 못하는 코드가 있을 수 있습니다. 이때는 CSS에서 @source를 사용해 추가 경로를 명시할 수 있습니다.

@import "tailwindcss";
@source "../packages/ui";

예를 들어 쇼핑몰 관리자와 사용자 화면을 하나의 저장소에서 관리하면서 공용 UI 패키지를 packages/ui에 두는 구조라면, 앱 쪽 Tailwind가 해당 패키지의 className까지 읽어야 합니다. 그렇지 않으면 공용 컴포넌트에는 className이 작성되어 있어도 CSS가 생성되지 않을 수 있습니다.

Tailwind v3 프로젝트라면 확인 위치가 다릅니다. v3에서는 tailwind.config.jscontent 배열에 실제 파일 경로가 포함되어야 합니다. v4의 @source 예시를 v3 프로젝트에 그대로 붙이거나, v3의 content 설정만 보고 v4 문제를 해결하려고 하면 진단이 어긋납니다. 먼저 프로젝트가 v3인지 v4인지 확인한 다음, 그 버전에 맞는 감지 방식을 봐야 합니다.

export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

위 예시는 Tailwind v3에서 흔히 보던 설정입니다. v4 프로젝트에서 이 구조가 반드시 필요한 것은 아니며, v4는 CSS 중심 설정으로 옮겨간 부분이 있습니다. 따라서 검색해서 나온 예시를 그대로 붙이기보다 현재 프로젝트 버전과 설치 방식을 먼저 맞춰야 합니다.

동적 className 문제 해결

동적 className 문제는 Tailwind를 React와 함께 쓸 때 가장 자주 만나는 유형입니다. 코드 자체는 짧고 깔끔해 보이지만, Tailwind가 필요한 클래스를 찾지 못해 프로덕션 빌드에서 스타일이 빠질 수 있습니다.

function StatusBadge({ tone }) {
  return <span className={`bg-${tone}-100 text-${tone}-700 px-2 py-1 rounded-full`}>상태</span>;
}

이 코드는 tonegreen이면 bg-green-100text-green-700이 만들어질 것처럼 보입니다. 하지만 Tailwind가 소스 파일을 읽을 때는 bg-green-100이라는 완성된 문자열이 없습니다. 그래서 CSS 생성 대상에서 빠질 수 있습니다.

해결은 조건을 없애는 것이 아니라, 조건별 결과를 완성된 문자열로 적어두는 방식입니다. 상태 값이 정해져 있는 배지, 버튼, 알림 박스라면 아래처럼 매핑 객체를 두는 것이 가장 안정적입니다.

const badgeClassNames = {
  success: "bg-green-100 text-green-700 px-2 py-1 rounded-full",
  warning: "bg-yellow-100 text-yellow-800 px-2 py-1 rounded-full",
  error: "bg-red-100 text-red-700 px-2 py-1 rounded-full",
};

function StatusBadge({ tone = "success" }) {
  const className = badgeClassNames[tone] ?? badgeClassNames.success;

  return <span className={className}>상태</span>;
}

이렇게 작성하면 Tailwind가 bg-green-100, bg-yellow-100, bg-red-100 같은 완성된 클래스를 소스에서 찾을 수 있습니다. 동시에 컴포넌트 관리도 쉬워집니다. 나중에 warning 상태만 색상을 바꾸고 싶을 때 문자열 조합 규칙을 추적하지 않아도 됩니다.

API에서 상태 값이 내려오는 경우에는 기본값 처리도 중요합니다. 서버에서 예상하지 못한 값이 내려왔을 때 className이 undefined가 되면 스타일이 깨지고, 화면에서도 원인을 찾기 어렵습니다. 그래서 매핑 객체를 사용할 때는 fallback 클래스를 함께 두는 편이 안전합니다.

const paymentStatusClassNames = {
  paid: "bg-blue-100 text-blue-700",
  pending: "bg-gray-100 text-gray-700",
  failed: "bg-red-100 text-red-700",
};

function PaymentStatus({ status }) {
  const toneClassName = paymentStatusClassNames[status] ?? "bg-gray-100 text-gray-700";

  return (
    <span className={`${toneClassName} inline-flex rounded-full px-2 py-1 text-sm`}>
      {status}
    </span>
  );
}

여기서 중요한 점은 toneClassName의 후보 문자열이 코드 안에 완성된 형태로 존재한다는 것입니다. 조합은 하더라도, Tailwind가 읽어야 할 핵심 유틸리티 클래스는 끊기지 않은 문자열로 남겨야 합니다.

임의 값 문법이 깨지는 경우

Tailwind의 대괄호 문법도 적용 누락처럼 보이는 문제를 만들 수 있습니다. w-[320px], grid-cols-[1fr_500px_2fr], h-[calc(100vh-64px)]처럼 기본 스케일에 없는 값을 직접 넣을 때 사용하는 문법입니다. 이 문법 자체는 유용하지만, 값 안에 공백이 들어가거나 문자열이 잘못 끊기면 클래스가 정상적으로 인식되지 않습니다.

예를 들어 CSS의 grid template column 값에는 공백이 필요합니다. 하지만 HTML class 속성에서 공백은 클래스 이름을 나누는 기준이기 때문에 그대로 쓰면 하나의 Tailwind 클래스가 아니라 여러 조각으로 분리됩니다.

// 잘못된 예
<div className="grid grid-cols-[1fr 500px 2fr]">
  ...
</div>

// 수정 예
<div className="grid grid-cols-[1fr_500px_2fr]">
  ...
</div>

Tailwind에서는 임의 값 안의 공백을 표현할 때 언더스코어를 사용하는 방식이 자주 쓰입니다. 화면 기준으로는 같은 grid 구조를 기대하더라도, 클래스 문자열 기준으로는 공백이 들어간 순간 하나의 클래스가 끊어질 수 있습니다.

calc()를 사용할 때도 비슷합니다. CSS에서는 calc(100vh - 64px)처럼 공백을 넣는 경우가 많지만, Tailwind className 안에서는 공백 때문에 클래스가 분리될 수 있습니다. 이럴 때는 공백 없이 작성하거나 Tailwind가 처리할 수 있는 형태로 정리합니다.

<main className="min-h-[calc(100vh-64px)]">
  ...
</main>

임의 값은 레이아웃을 빠르게 맞출 때 편하지만, 반복되는 값이라면 테마 토큰이나 별도 유틸리티로 정리하는 편이 낫습니다. 특히 여러 화면에서 같은 높이 계산을 반복한다면 매번 calc()를 복사하는 것보다 레이아웃 컴포넌트나 CSS 변수로 분리하는 것이 유지보수에 유리합니다.

상태 클래스와 우선순위 점검

클래스가 생성됐는데도 원하는 스타일이 보이지 않는다면 상태 variant와 CSS 우선순위를 확인해야 합니다. hover:bg-blue-600은 기본 상태가 아니라 hover 상태에서만 적용됩니다. disabled:bg-gray-300은 실제 disabled 속성이 있을 때만 적용됩니다. className에 들어갔다고 해서 항상 화면에 바로 보이는 것은 아닙니다.

function SaveButton({ disabled }) {
  return (
    <button
      disabled={disabled}
      className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:bg-gray-300 disabled:text-gray-500"
    >
      저장
    </button>
  );
}

위 코드에서 기본 배경은 bg-blue-500입니다. 마우스를 올렸을 때만 hover:bg-blue-600이 보이고, 버튼이 비활성화됐을 때만 disabled:bg-gray-300이 보입니다. 그래서 상태 클래스 문제를 볼 때는 “클래스가 있느냐”와 함께 “그 상태가 실제 DOM에서 성립했느냐”를 확인해야 합니다.

기존 CSS와의 충돌도 자주 나옵니다. 레거시 퍼블리싱 코드에 .button, .card, .title 같은 클래스가 남아 있고, 같은 요소에 Tailwind 유틸리티를 함께 붙이면 우선순위에 따라 일부 스타일이 덮일 수 있습니다. 워드프레스 본문 스타일, 관리자 템플릿 CSS, 기존 SCSS 파일 위에 Tailwind 컴포넌트를 얹는 상황에서 특히 자주 확인해야 합니다.

개발자 도구에서 요소를 선택하고 Styles 패널을 보면 어떤 속성이 적용됐고 어떤 속성이 취소됐는지 볼 수 있습니다. Tailwind 클래스가 생성되어 있는데 취소선이 그어져 있다면 감지 문제가 아니라 우선순위 문제입니다. 이때는 클래스 작성 방식보다 CSS 로딩 순서, selector 강도, 같은 속성을 건드리는 기존 규칙을 먼저 봐야 합니다.

실무에서 확인하는 순서

Tailwind CSS 적용 누락 해결 후 동적 클래스 분리, 조건별 완성 문자열, 상태 variant를 확인하는 체크리스트

실제 작업에서는 아래 순서로 보면 시간이 덜 걸립니다. 처음부터 !important나 임의 클래스를 추가하면 당장은 화면이 맞아 보여도 원인을 숨기기 쉽습니다. 특히 팀 프로젝트나 블로그 예제처럼 나중에 다시 읽어야 하는 코드는 수정 이유가 남아야 합니다.

  1. 개발자 도구에서 요소에 class 속성이 실제로 들어갔는지 확인합니다.
  2. 해당 클래스의 CSS 규칙이 Styles 패널에 존재하는지 확인합니다.
  3. CSS 규칙이 없다면 Tailwind 감지 문제나 동적 클래스 문제를 의심합니다.
  4. CSS 규칙은 있는데 취소선이 있다면 기존 CSS와 우선순위 충돌을 확인합니다.
  5. 상태 variant라면 hover, focus, disabled, data 속성이 실제로 조건을 만족하는지 확인합니다.
  6. 설정이나 전역 CSS를 수정했다면 개발 서버를 재시작하고 프로덕션 빌드도 확인합니다.

예를 들어 상품 카드 컴포넌트에서 품절 상태일 때만 회색 배경이 적용되지 않는다고 가정해보겠습니다. 이때 전체 Tailwind 설정을 먼저 바꿀 필요는 없습니다. soldout 상태에서 실제 className이 무엇으로 찍히는지, 그 클래스가 완성된 문자열로 코드에 있는지, 해당 클래스의 CSS가 생성됐는지를 순서대로 보면 됩니다.

const productCardClassNames = {
  normal: "border-gray-200 bg-white text-gray-900",
  soldout: "border-gray-200 bg-gray-100 text-gray-400",
  event: "border-blue-200 bg-blue-50 text-blue-900",
};

function ProductCard({ state = "normal", name }) {
  const toneClassName = productCardClassNames[state] ?? productCardClassNames.normal;

  return (
    <article className={`${toneClassName} rounded-xl border p-4`}>
      <h3 className="text-base font-semibold">{name}</h3>
    </article>
  );
}

이런 식으로 상태와 스타일 후보를 분리하면 디버깅 포인트가 명확해집니다. soldout이 안 보인다면 상태 값이 잘못 들어왔는지, 매핑 객체에 해당 키가 있는지, 완성된 클래스가 Tailwind에 감지됐는지를 차례대로 확인할 수 있습니다.

반대로 색상 값이 사용자 입력이나 관리자 설정에서 자유롭게 들어오는 구조라면 Tailwind 클래스 조합보다 CSS 변수나 inline style이 더 적합할 수 있습니다. Tailwind는 정해진 디자인 토큰과 반복 UI에 강하고, 예측 불가능한 런타임 값까지 모두 유틸리티 클래스로 처리하는 데에는 한계가 있습니다.

정리

Tailwind CSS 클래스가 적용되지 않을 때는 먼저 문제를 분류해야 합니다. 전체 클래스가 안 먹는다면 전역 CSS와 빌드 연결을 보고, 특정 컴포넌트만 문제라면 소스 감지 범위를 봅니다. 조건부 색상이나 크기만 빠진다면 동적 className을 의심하고, 상태 클래스가 보이지 않는다면 DOM 상태와 variant 조건을 확인합니다.

React와 Next.js에서는 props나 API 값으로 className을 조합하기 쉽지만, Tailwind는 완성된 클래스 문자열을 소스에서 찾는 방식으로 CSS를 생성합니다. 그래서 bg-${color}-500처럼 중간을 조합하는 코드보다, success, warning, error 같은 상태를 완성된 클래스 문자열에 매핑하는 방식이 안정적입니다.

문제를 해결할 때는 브라우저에 class가 찍혔는지, CSS 규칙이 생성됐는지, 다른 규칙에 덮였는지를 나눠서 확인하면 됩니다. 이 순서만 지켜도 Tailwind 적용 누락 문제는 대부분 재현 가능한 형태로 좁혀지고, 임시방편 없이 수정할 수 있습니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기