SCSS 중독자를 위한 Tailwind CSS 실전 마이그레이션 가이드

CSS 파일 왔다 갔다 하기가 지겨워진 당신에게. SCSS의 장점을 흡수하고 단점은 버리는, 가장 실무적인 Tailwind 정착 튜토리얼.

Jun Noh

난 왜 tailwind로 써보려고 하는가

나는 리액트 컴포넌트 파일(.tsx)과 스타일 파일(.scss)을 나란히 띄워놓고 개발하는 것을 좋아했다. .container { .wrapper { ... } }로 이어지는 그 중첩(Nesting) 구조가 주는 안정감은 절대적이었다.

하지만 혼자 여러 프로젝트를 이렇게 구성하면서 유지보수의 한계가 명확히 보였다.

SCSS가 병목이 된 순간들 (Cons)

  1. 작명 지옥: 고작 div 하나 감싸는데 .inner-wrapper, .content-box 같은 무의미한 이름을 짓느라 뇌 리소스를 쓴다.
  2. 컨텍스트 스위칭: 마크업(HTML) 구조를 바꾸면, 반드시 스타일 파일(CSS)로 넘어가서 또 수정해야 한다. 왔다 갔다 하는 시간이 전체 개발 시간의 30%는 잡아먹는다.
  3. 죽은 코드: 컴포넌트는 지웠는데, styles.scss에 남아있는 클래스는 무서워서 못 지운다. 사이드 이펙트가 무섭기 때문이다.

결국 나는 “우아한 코드”라는 겉멋을 버리고 “압도적인 생산성”을 택하기로 했다. 이 글은 SCSS의 유산(Legacy)을 Tailwind 방식으로 재해석하고, Tailwind를 단순 암기가 아닌 문법으로 이해하여 실무에 적용하는 가이드다.


Part 1. 문법 해부: 클래스는 ‘단어’가 아니라 ‘문장’이다

Tailwind를 포기하는 사람들의 공통점은 “그 많은 클래스를 어떻게 다 외워?”라고 묻는다는 것이다. 접근이 틀렸다. Tailwind는 암기과목이 아니라 작문이다.

SCSS에서 우리는 이렇게 “상황”을 묘사했다.

“이 버튼은 평소엔 파란색인데(base), 마우스를 올리면 진해지고(:hover), 모바일에서는 꽉 차게(@media) 보여줘.”

Tailwind는 이 문장을 콜론(:)과 대시(-)로 연결된 하나의 문법으로 압축한다.

[기본(모바일) 파랑] + [호버하면 진한 파랑] + [기본(모바일) 꽉 차게] + [PC에선 작게]

bg-blue-500 hover:bg-blue-600 w-full md:w-auto

이 구조만 파악하면 공식 문서를 뒤질 필요가 없다. 상세하게 뜯어보자.

1. 접두사(Prefix): “언제” 스타일을 적용할까?

모든 조건부 스타일링은 접두사로 시작한다. SCSS의 @media:pseudo-class를 완벽히 대체한다.

A. 상태(State) 접두사

  • hover: : 마우스 올렸을 때. (모바일 터치 이슈 주의)
  • active: : 클릭하는 찰나의 순간. 버튼 눌림 효과에 필수다.
  • focus: : 인풋 창 등을 클릭했을 때.
  • focus-visible: : 마우스 클릭 땐 포커스 링을 숨기고, 키보드 탭(Tab) 이동 때만 보여준다. 디자인을 해치지 않으면서 접근성을 챙기는 꿀팁이다.
  • disabled: : 요소가 비활성화되었을 때.

B. 반응형(Responsive) 접두사 (Mobile First)

Tailwind는 작은 화면이 기본이다. 화면이 커질수록 덮어쓰는 방식이다. SCSS의 min-width 미디어 쿼리와 같다.

  • (없음) : 0px ~ (모바일 기본)
  • sm: : 640px ~
  • md: : 768px ~ (태블릿) <- 가장 많이 씀
  • lg: : 1024px ~ (노트북)
  • xl: : 1280px ~ (데스크톱)

[주의] <div class="hidden md:block">은 “모바일에서 숨기고(hidden), 태블릿부터 보여줘(block)“라는 뜻이다. 반대가 아니다.

C. 다크모드 접두사

  • dark: : 상위 요소(보통 html 태그)에 dark 클래스가 있을 때만 작동한다.
    <div className="bg-white text-black dark:bg-gray-900 dark:text-white">
      자동으로 밤낮이 바뀌는 박스
    </div>

2. 관계형 접두사: SCSS 중첩(Nesting)의 대체

SCSS 사용자가 가장 그리워하는 것이 &:hover.parent:hover .child 같은 중첩 문법이다. Tailwind는 이걸 grouppeer로 해결한다.

A. group & group-hover: (부모 반응)

부모에게 group 클래스를 주고, 자식에게 group-{상태}:를 쓴다.

<div className="group card p-4 border hover:border-blue-500">
  {/* 부모(card)에 마우스가 올라가면 제목 색상이 바뀜 */}
  <h3 className="text-gray-900 group-hover:text-blue-500 transition-colors">
    카드 제목
  </h3>
  <p className="text-gray-500 group-hover:text-gray-700">
    내용...
  </p>
</div>

이 방식의 장점은 SCSS 파일을 열어보지 않고도 인터랙션 흐름이 보인다는 것이다.

B. peer & peer-focus: (형제 반응)

인풋에 포커스가 갔을 때, 바로 뒤에 있는 라벨이나 아이콘을 움직이고 싶다면? 이전 형제(Previous Sibling)에 peer를 준다.

<div className="relative">
  <input type="text" className="peer border p-2" placeholder=" " />
  {/* 인풋(peer)이 포커스되면 라벨 색상이 파랗게 변함 */}
  <span className="text-gray-400 peer-focus:text-blue-500">이메일</span>
</div>

3. 접미사와 수정자: 디테일 깎기

정해진 값(w-96, bg-red-500)만 써야 한다면 1인 개발자는 답답해 죽는다. 이때 필요한 게 튜닝 옵션이다.

A. 투명도 조절 (/)

rgba를 칠 필요가 없다. 색상 뒤에 슬래시를 붙여라.

  • bg-black/50 : 50% 투명한 검은색.
  • border-white/10 : 아주 희미한 테두리 (다크모드 경계선으로 아주 좋다).

B. 임의 값 ([], Arbitrary Values)

“딱 한 번만 쓸 건데 tailwind.config.js 수정하기 귀찮을 때” 쓴다. 픽셀 단위 집착을 버리지 못했다면 이걸로 시작해라.

  • w-[350px] : 정확히 350px 너비.
  • bg-[#123456] : 커스텀 헥스 코드.
  • z-[100] : z-index 100.
  • grid-cols-[1fr_500px] : 복잡한 그리드 레이아웃.

Part 2. 프로덕션 레벨 세팅 (그대로 따라하기)

문법을 익혔어도 도구가 없으면 Tailwind는 그저 “긴 문자열”일 뿐이다. 쾌적한 개발을 위한 필수 세팅 3가지를 순서대로 적용해 보자.

Step 1. VS Code Extension 설치 (필수)

VS Code 마켓플레이스에서 [Tailwind CSS IntelliSense]를 검색해서 설치한다.

  • 기능 1: 클래스 자동완성. (기억 안 나면 fle... 까지만 쳐도 나온다)
  • 기능 2: 마우스 호버 시 실제 CSS 값(padding: 1rem) 미리 보기.
  • 기능 3: 색상 네모 박스 표시.

Step 2. Prettier Plugin: 자동 정렬

HTML 클래스가 뒤죽박죽이면 유지보수가 불가능하다. “내가 flex를 앞에 썼던가?” 고민하지 말고 기계한테 맡겨라.

  1. 패키지 설치:
    npm install -D prettier prettier-plugin-tailwindcss
  2. Prettier 설정 파일(.prettierrc 등)에 플러그인 추가 (자동으로 인식되기도 하지만 명시하는 게 좋다).
    {
      "plugins": ["prettier-plugin-tailwindcss"]
    }
  3. 이제 파일 저장(Save)을 누르면 class="p-4 flex bg-red-500"이 Tailwind 권장 순서인 class="flex bg-red-500 p-4"로 자동 정렬된다. 마음의 평화가 온다.

Step 3. cn 유틸리티: Mixin의 현대적 대체

Tailwind를 쓰다 보면 조건부 스타일링 때문에 템플릿 리터럴이 지저분해진다. 리액트 생태계의 표준인 cn 함수를 만들자.

  1. 패키지 설치:
    npm install clsx tailwind-merge
  2. 유틸리티 함수 생성 (src/lib/utils.ts):
    import { type ClassValue, clsx } from "clsx";
    import { twMerge } from "tailwind-merge";
    
    export function cn(...inputs: ClassValue[]) {
      // clsx: 조건부 객체 { 'bg-red': isError } 처리를 담당
      // twMerge: 'px-4' 뒤에 'px-8'이 오면 앞의 것을 지워줌 (충돌 해결)
      return twMerge(clsx(inputs));
    }

Part 3. 실전 활용: 나만의 컴포넌트 만들기

이제 SCSS의 @mixin 없이 재사용 가능한 버튼 컴포넌트를 만들어보자. 위에서 만든 cn 함수가 핵심이다.

// src/components/Button.tsx
import { cn } from "@/lib/utils";

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "primary" | "secondary" | "danger";
};

export function Button({ className, variant = "primary", ...props }: ButtonProps) {
  return (
    <button
      className={cn(
        // 1. 공통 스타일 (Base)
        "inline-flex items-center justify-center rounded-md px-4 py-2 font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none",
        
        // 2. 변형 스타일 (Variants) - SCSS의 Modifier
        variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
        variant === "secondary" && "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500",
        variant === "danger" && "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
        
        // 3. 외부 주입 스타일 (Override)
        // 사용자가 className="w-full"을 넣으면 여기서 병합됨
        className
      )}
      {...props}
    />
  );
}

이제 사용할 때는 이렇게 쓴다.

<Button variant="danger" className="w-full shadow-lg">
  삭제하기
</Button>

SCSS 파일 하나 없이, 완벽하게 캡슐화되고 확장 가능한 컴포넌트가 탄생했다.


결론: “예쁜 코드 파일”보다 “빠른 제품”을 원한다면

SCSS 파일이 깔끔하게 정리되어 있을 때의 그 희열, 나도 안다. 하지만 1인 비즈니스에서 중요한 건 정리된 코드’가 아니라 ‘동작하는 서비스다.

Tailwind 문법에 익숙해지면, 머릿속에 그린 UI가 타자 치는 속도 그대로 브라우저에 렌더링 되는 몰입을 경험하게 된다. “어떻게 구현하지?”를 고민하며 구글링하는 시간이 사라지고, “무엇을 만들까?”에만 집중하게 되는 것.

그게 내가 scss에서 Tailwind를 선택한 진짜 이유다. 지금 당장 터미널을 열고 npm install tailwindcss를 입력해 보라. 새로운 세상이 열릴 것이다.

마침.

다른 글 보기