TailwindCSS의 동작

rabbit jack·2025년 6월 24일

React

목록 보기
1/4

1. 소개

tailwindCSS 환경에서 반응형 웹 개발 효율을 높이기 위해 아래와 같은 함수를 만들었다.

export function parseResponsiveMarginX(values: number[]): string {
  const breakpoints = ['mx', 'sm:mx', 'md:mx', 'lg:mx', 'xl:mx'];

  if (values.length === 0) return '';

  const safeValues = [...values];

  // 5보다 작을 경우 마지막 값을 복사해서 채움
  while (safeValues.length < 5) {
    safeValues.push(safeValues[safeValues.length - 1]);
  }

  // 5보다 크면 앞에서 5개만 사용
  const trimmed = safeValues.slice(0, 5);

  return trimmed
    .map((val, idx) => `${breakpoints[idx]}-[${val}rem]`)
    .join(' ');
}

parseResponsiveMarginX([2,4,6,8,10]) 같은 방식으로 함수를 호출한 후, 리턴값을 className에 첨부하여 사용해보았다.

하지만, 레이아웃은 제대로 동작하지 않았다. 정확히는 정상과 비정상 상태를 불규칙하게 오갔다.

알고보니 TailwindCSS는 동적으로 생성된 className을 인지하지 못하며, 이는 공식문서에서도 강조하고있다.

그렇다고 className 내의 모든 설정에 상수 문자열만 넣어야 하는것은 아니다.

여러 포스팅과 공식문서를 찾아보며 className을 효율적으로 사용하며 주의할 점을 알아보겠다.

2. TailwindCSS의 등장 배경

기존 HTMLCSS의 작동 원리를 대략적으로 알아보고, 그 한계점을 검토하여 TailwindCSS가 탄생하게된 배경을 알아보자.

2-1. HTML과 CSS

1) 적용원리

https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model
https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path

우선 기본부터 짚고 넘어가겠다.

브라우저가 HTMLCSS를 이용하여 문서를 구성하는 순서
1. 브라우저가 HTML 파싱을 시작
2. <link rel="stylesheet" href="style.css" />를 만나면 CSS 파일 로딩
3. HTML은 DOM Tree, CSS는 CSSOM Tree로 파싱됨
4. 이 둘을 합쳐 Render Tree를 구성
5. Render Tree를 기반으로 브라우저가 화면에 스타일을 반영

2) 개발 방식

HTMLCSS만을 이용하여 웹페이지를 구성하는 전통적인 방식은 아래 절차를 따릅니다.

<!-- HTML -->
<p class="highlight">Lorem ipsum</p>
/* CSS */
.highlight {
  color: red;
  font-weight: bold;
  background-color: yellow;
  padding: 1rem;
}

이때 CSS는 Selector 를 기반으로 HTML element를 찾으며, 우선순위에 따라 적용 됩니다.

3) 한계점

htmlcss파일을 분리하여 개발하고, 별도로 매핑하는 방식은 여러 한계점을 낳게 됩니다.

  • 전역 네임스페이스로 인한 이름 충돌
  • 사용하지 않는 css 코드 추적의 어려움
  • 참조 관계 추적의 어려움으로 인한 수정 및 재사용의 어려움
  • 스타일 중복
  • 우선순위 문제 해결 어려움

2-2. TailwindCSS의 탄생

css 파일 분리에 따른 비효율 문제를 타파하고자, css 작성을 하지 않고, HTML에 유틸리티 클래스만 조합해서 스타일을 구현하는 TailwindCSS가 탄생하게 됩니다.

3. TailwindCSS의 동작

TailwindCSSJS가 아니라 빌드시스템이다.

컴파일 단계에서 tailwind.config.js 파일을 바탕으로, 실제로 사용된 className들에 대해서만 postCSS 플러그인CSS 파일을 생성한다.

즉, TS와 마찬가지로, vite, webpack 등에 의해 HTML/JSX/TSX파일을 정적 분석하여 css를 만드는 빌드 시스템이다.

그렇기에 런타임 도중에 결정되는 문자열 정보들은 className 내에서 인지할 수 없다.

달리 말하면 컴파일 단계에서 정적 분석이 가능한 문자열은 인지 가능하다.

즉, className에 무조건 상수문자열만 할당해야 하는것은 아니다.

// ✅ 안전한 사용 예
const colorClass = "text-red-500";
return <div className={colorClass}>Hello</div>;

colorClass가 파일 안에서 하드코딩된 문자열이므로 Tailwind가 정적 분석 가능하다.

// ❌ 위험한 사용 예
const color = "red";
return <div className={`text-${color}-500`}>Hello</div>;

하지만 위와 같은 경우, 런타임에 조합된 클래스 명이므로 Tailwind 정적 분석기에서 인지 할 수 없다.

// ❌ 위험한 사용 예
function Button({ color, children }) {
  return <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>{children}</button>;
}

props는 런타임에 결정되므로, 위 케이스 또한 인지 불가능하다.

// ✅ 안전한 사용 예
function Button({ color, children }) {
  const colorVariants = {
    blue: "bg-blue-600 hover:bg-blue-500",
    red: "bg-red-600 hover:bg-red-500",
  };
  return <button className={`${colorVariants[color]} ...`}>{children}</button>;
}

문자열 리터럴이라고 무조건 안되는것은 아니다. 실행중 분기점 형성 또한 위처럼 가능하다.

// ✅ 안전한 사용 예
<div className={isError ? "text-red-500" : "text-gray-400"} />

명백한 분기로 구성되었으므로 2개의 스타일 모두 인식 가능하다.
isError라는 변수로 분기점이 형성되어도, className에 할당되어야 할 문자열 자체는 런타임 중에 알 수 있으므로, 문제 없다.

// ❌ 위험한 사용 예
const dangerClass = "text-red-500";
const safeClass = "text-gray-400";
const color = isError ? dangerClass : safeClass;

<div className={color} />

반면, 이 경우는 불가능하다. 변수명은 정적이지만, 그 안의 문자열은 정적으로 분석할 수 없다고 간주한다.

// ✅ 안전한 사용 예
const colorMap = {
  red: "text-red-500",
  blue: "text-blue-500",
  green: "text-green-500",
} as const;

return <div className={colorMap[color]}>...</div>;

모든 경우의 수를 미리 나열 한 경우, 이 또한 Tailwind가 코드 전체를 분석할 수 있으므로 안전하다.

// ✅ 안전한 사용 예
import clsx from "clsx";

<div className={clsx("p-4", isDark && "bg-black", isError && "text-red-500")} />

clsx도 결국 정적인 문자열 조합이라 Tailwind로 분석 가능하다.

// ✅ 안전한 사용 예
const buttonConfig = {
  // Colors
  primary: {
    bgColor: 'bg-primary-500',
    color: 'text-white',
    outline:
      'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
  },
  secondary: {
    bgColor: 'bg-secondary-500',
    color: 'text-white',
    outline:
      'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
  },

  // Sizes
  small: 'px-3 py-2',
  medium: 'px-4 py-2',
  large: 'px-5 py-2',
};


  <motion.button
    whileTap={{ scale: 0.98 }}
    className={`
    rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
    ${buttonConfig[size]}
    ${outlined && buttonConfig[color].outline}
    ${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
    onClick={onClick}
    type="button"
    tabIndex={0}
  >
    {children}
  </motion.button>

놀랍게도 위 방법도 가능하다. 어쨋든 문자열 자체를 인지하는데에 상관 없다면, 분기점 형성 방식은 아무래도 좋은 모양이다.

4. 정리

TailwindCSS는 JS가 아니라 빌드시스템이다.

즉, 런타임 중에 실행되는것이 아니라, TS와 마찬가지로 vite, webpack 등에 의해 정적으로 css를 만드는 빌드 시스템이다.

한마디로, 런타임중에 결정되는 속성들에 대해선 알 방법이 없다.

그래도 className을 효과적으로 쓰기 위해선 적어도 아래와 같은 규칙을 준수하면 된다.

  1. 템플릿 리터럴(...${}...)로 조합하는 방식은 주의를 기울일 것
    • 이 방법이 안된다는 뜻은 아니다. 하지만 초심자라면 가급적 주의를 기울여야한다.
  2. className에 변수를 할당하려면, 그 변수 안에 “하드코딩된 문자열”이 있어야 함
  3. 조건 분기(? :, &&, if)는 가능하되, 문자열 리터럴이 명확히 드러나야 안전하다
  4. 직접 하드코딩한 문자열이 할당된 변수여도, 변수내 변수 같은 형태로는 쓰지 말것

한마디로 아래 사항은 절대적으로 명심해야한다.

Tailwind는 문자열을 빌드타임에 직접 볼 수 있어야 CSS를 만든다.

5. 마무리

런타임 언어인 JS(React,Vue)등에는 컴파일에 대한 개념이 없다.

모든것을 런타임에 결정하며, 범용적이고 쉬운 사용법을 지향하지만 그로인해 협업 및 유지보수 환경에 많은 한계점을 낳기도 했다.

그렇기에 현대 프론트엔드에서는 아래처럼 런타임언어 위에 컴파일 타임 개념을 인위적으로 얹은 구조가 탄생하게 되었다.

  • TypeScript (정적 타입 검사)
  • Babel (트랜스파일)
  • Webpack/Vite (번들링 & 정적 분석)
  • TailwindCSS (정적 CSS 추출)

이로 인해 단일파일 내에서 런타임 영역과 정적 컴파일 영역이 뚜렷한 구분 없이 나뉘는 다소 기형적인 형태를 낳게 되었으며, 이로인해 크고 작은 해프닝이 발생하기 쉽다.

필자는 Vanilla JSHTML, CSS 만을 사용하다가 React & Typescript & TailwindCSS 환경을 처음 접하면 많은 부분이 헷갈렸다.

React라는 다소 생소한 개념을 마주하게 되고, jsxtsx 같은 기존에 볼 수 없었던 자료형도 다루게 되며, 심지어 같은 파일 내에 javascripttypescript, React Component, TailwindCSS 등 여러 개념이 섞이기 때문이다.

그렇기에numberNumber를 혼재하며 사용하거나, 컴파일 단계에 결정되어야할 코드 영역에 런타임 변수를 넣는 등의 실수를 반복할때가 많다.

이 포스팅이 미래의 나와 초보 개발자들에게 유용한 지침서가 되길 바란다.

6. 참고

https://tailwindcss.com/docs/detecting-classes-in-source-files
https://velog.io/@arthur/Tailwind-CSS-%EC%97%90%EC%84%9C-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%95%A0%EB%8B%B9%ED%95%98%EA%B8%B0

0개의 댓글