ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #4 Tailwind v4 - 고생의 시작
    Design System Library 2026. 4. 17. 20:57

    Tailwind v4 올리면 끝일 줄 알았는데

    Tailwind CSS v4 나오고 나서 관련 글을 꽤 많이 봤다.

    대부분 비슷했다.

    • 새 문법은 이렇다
    • config는 이렇게 바뀌었다
    • @theme는 이렇게 쓴다
    • v3에서 v4는 이렇게 옮기면 된다

    틀린 말은 아니다. 오히려 잘 정리돼 있었다.

    근데 막상 실제 프로젝트에 넣고 돌리기 시작하면 궁금한 건 다른 쪽이었다.

    • 뭐가 조용히 깨지나
    • 빌드는 되는데 왜 화면은 안 바뀌나
    • 다크모드는 진짜 괜찮나
    • 기존 컴포넌트는 그대로 돌아가나
    • 에러 없이 안 되는 건 뭔가

    나는 7onic이라는 디자인 시스템을 운영하고 있다.
    Tailwind v3 사용자도 있고, v4 사용자도 있다.

    즉 내 입장에서는 그냥 개인 프로젝트 하나 올리는 문제가 아니었다.

    • 같은 JSON 토큰 소스를 유지해야 하고
    • 같은 컴포넌트가 두 버전에서 돌아야 하고
    • 사용자 입장에서는 차이를 최대한 못 느끼게 해야 했다

    그래서 이번 v4 대응은 단순 업그레이드가 아니라, 실제 운영 이슈에 가까웠다.

    Config 파일이 사라진 게 아니라 CSS로 옮겨갔다

    v3까지는 모든 설정의 중심이 tailwind.config.js였다.

    module.exports = {
    theme: {
    extend: {
    colors: {
    primary: {
    DEFAULT: 'rgb(var(--color-primary-rgb) / <alpha-value>)',
    hover: 'rgb(var(--color-primary-hover-rgb) / <alpha-value>)',
    },
    },
    borderRadius: {
    sm: 'var(--radius-sm)',
    lg: 'var(--radius-lg)',
    },
    },
    },
    }
     

    v4에서는 같은 역할을 CSS 안에서 처리한다.

    @theme {
    --color-primary: #15A0AC;
    --color-primary-hover: #107A84;
    --radius-sm: 2px;
    --radius-lg: 8px;
    }
     

    처음 보면 단순히 “설정 위치가 바뀌었네?” 정도로 느껴진다.

    근데 실제로 작업해 보면 생각보다 느낌이 다르다.

    v3는 JS config를 수정해서 CSS를 생성하는 구조였다면,
    v4는 CSS를 작성하고 그걸 기준으로 유틸리티가 생성된다.

    이 차이가 디버깅할 때 꽤 크게 느껴졌다.

    예전엔 config 파일부터 봤다면, 지금은 생성된 CSS를 먼저 보게 된다.

    개인적으로는 이 방향이 더 낫다고 느꼈다.

    @theme inline 때문에 다크모드가 멈췄다

    이번 작업에서 가장 오래 걸린 문제였다.

     

    처음엔 문서 보고 자연스럽게 이렇게 썼다.

    라이트 모드에서는 아무 문제도 없었다.

    그래서 그대로 진행했다.

     

    근데 다크모드 켜는 순간 아무 변화가 없었다.

    처음엔 내가 dark mode 토글 코드를 잘못 짠 줄 알았다.


    그다음엔 .dark 클래스 문제인 줄 알았다.
    그 다음엔 CSS import 순서 문제인 줄 알았다.

     

    다 아니었다.

    문제는 @theme inline이었다.

    이 방식은 런타임에 CSS 변수를 보는 게 아니라, 빌드 시점 값으로 고정해 버린다.

    /* What I thought was happening */
    .bg-primary { background-color: var(--color-primary); }

    /* What @theme inline actually generated */
    .bg-primary { background-color: #15A0AC; }
     

    즉 브라우저에서 변수는 바뀌고 있었지만, 실제 클래스는 더 이상 변수를 안 보고 있었다.

    당연히 다크모드가 안 바뀐다.

    해결은 단순했다.

    inline을 빼고 plain @theme로 바꾸는 것.

    빌드 사이즈는 조금 늘었다.
    하지만 다크모드 되는 게 훨씬 중요했다.

    런타임 테마 변경이 있다면 이건 꼭 체크하는 게 좋다.

    Plugin 대신 @utility가 된 건 꽤 좋았다

    v3에서 커스텀 유틸리티를 만들려면 JavaScript plugin이 필요했다.

    plugins: [
    function({ addUtilities }) {
    addUtilities({
    '.icon-sm': {
    width: 'var(--icon-size-sm)',
    height: 'var(--icon-size-sm)',
    },
    '.icon-md': {
    width: 'var(--icon-size-md)',
    height: 'var(--icon-size-md)',
    },
    })
    },
    ]
     

    v4는 그냥 CSS다.

    @utility icon-sm {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    }
    @utility icon-md {
    width: var(--icon-size-md);
    height: var(--icon-size-md);
    }
     

    나는 이런 유틸리티를 꽤 많이 자동 생성하고 있다.

    • icon sizes
    • durations
    • z-index layers
    • focus rings
    • animations

    예전보다 훨씬 읽기 쉽고, 관리도 편해졌다.

    JS plugin을 머리로 해석할 필요 없이 CSS 파일만 보면 된다.

    @source는 쉬워 보이는데 한번 틀리면 허무하다

    기존 content 배열은:

    content: ['./src/**/*.{ts,tsx}', './app/**/*.{ts,tsx}']
     

    v4에서는 이렇게 바뀐다.

    @source "../src/**/*.{ts,tsx}";
    @source "../app/**/*.{ts,tsx}";
     

    겉으로 보면 단순 변경이다.

    근데 여기서 한 번 당했다.

    경로 기준이 프로젝트 루트가 아니라 CSS 파일 기준 상대경로였다.

    예전 감각대로 적고 빌드 돌렸더니 결과 CSS가 거의 비어 있었다.

    에러 없음.
    경고 없음.
    그냥 생성 안 됨.

    이런 버그가 제일 오래 걸린다.

    Variant stacking 순서 변경은 듀얼 지원에서 꽤 아프다

    이건 일반 사용자보다 라이브러리 운영자가 더 크게 느낄 부분이다.


    v3:

    className="[&_div]:data-[state=checked]:bg-primary"


    v4:

    className="data-[state=checked]:[&_div]:bg-primary"
     

    둘 다 문법 에러는 아니다.

    근데 한쪽에서는 되고, 한쪽에서는 안 된다.

    Switch 컴포넌트 작업하다 여기서 꽤 시간을 썼다.

    같은 Tailwind인데 내부 룰이 달라진다는 건, 듀얼 지원 입장에선 생각보다 피곤하다.

    다크모드는 좋아졌는데 현실은 더 복잡해졌다

    v3는 단순했다.

    darkMode: 'class'
     

    .dark 붙이면 끝이었다.

    v4는 시스템 다크모드(prefers-color-scheme)를 자연스럽게 활용한다.

    좋은 방향이다.

    근데 실제 서비스는 보통 세 가지가 다 필요하다.

    • 시스템 설정 따라가기
    • 강제 라이트 모드
    • 강제 다크 모드

    결국 이렇게 가게 됐다.

    /* 1. OS auto-detection (respect user's system setting) */
    @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
    --color-background: var(--color-gray-900);
    --color-text: var(--color-gray-100);
    }
    }

    /* 2. Manual dark (explicit override) */
    :root[data-theme="dark"],
    :root.dark {
    --color-background: var(--color-gray-900);
    --color-text: var(--color-gray-100);
    }
     

    예쁘진 않다.

    근데 서비스는 원래 예쁘게만 만들 수가 없다.


    아무도 말 안 해준 outline flash

    한동안 왜 input 포커스 느낌이 이상한지 몰랐다.

    자세히 보니까 outline 색이 살짝 fade 되면서 들어왔다.

    원인은 v4에서 transition-colors 범위가 넓어지면서 outline-color까지 포함된 것.

    className="transition-colors focus:outline-2 focus:outline-primary"
     

    해결은 기본 상태를 투명 outline으로 맞춰주는 거였다.

    className="outline-transparent transition-colors focus:outline-2 focus:outline-primary"
     

    작은 수정인데 체감은 꽤 컸다.


    Opacity는 드디어 정상화됐다

    이건 진짜 좋았다.

    v3에서 bg-primary/50 같은 걸 CSS 변수 토큰과 함께 쓰려면 RGB 채널 분리가 필요했다.

    --color-primary-rgb: 21 160 172;
     

    그리고:

    rgb(var(--color-primary-rgb) / <alpha-value>)
     

    솔직히 우아하진 않았다.

    v4는 내부적으로 color-mix()를 사용한다.

    background-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
     

    즉 그냥 된다.

    토큰 파이프라인도 단순해지고 유지보수도 줄었다.


    그래서 갈아탈 만하냐고 묻는다면

    나라면 갈아탄다.

    좋아진 점이 분명하다.

    • CSS-first 구조
    • 커스텀 유틸리티 단순화
    • opacity 처리 개선
    • config 부담 감소
    • 전체 구조의 투명성 증가

    다만 기존 v3 프로젝트가 이미 크다면, 문법부터 보지 말고 먼저 이걸 본다.

    • 다크모드
    • 테마 토글
    • variant 사용 패턴
    • source 경로
    • 실제 UI transition 차이

    문법 변경은 금방 대응된다.

    조용히 깨지는 건 직접 테스트해야 보인다.


    한 줄로 정리하면

    Tailwind v4는 단순한 새 버전이라기보다,

    Tailwind가 CSS를 다루는 방식 자체가 바뀐 버전에 가깝다.

    그걸 이해하고 올리면 꽤 만족스럽고,

    예전 감각 그대로 올리면 생각보다 오래 걸린다.

     

     

     7onic Design System

     

    7onic Design System

    7onic 디자인 시스템은 디자인과 코드의 불일치를 제로로. Figma 토큰을 단일 소스로, 디자이너가 직접 검증한 Radix UI 기반 React 컴포넌트. AI 협업 개발에 최적화된 오픈소스. npm install 또는 CLI 소스

    7onic.design

    본 글은 본인의 7onic 디자인 시스템 영문 원문 블로그에서 가져 왔습니다. 

     

     

     

     

7onic Blog.