Shadcn이 해결한 테일윈드의 가장 큰 문제점

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

스크립트

00:00:00ShadCN이 오늘날 Tailwind의 가장 큰 문제를 해결하기 위한 린터를 방금 출시했습니다.
00:00:04바로 디자인 시스템 문제입니다. AI 에이전트의 부상 속에서도 Tailwind에는 이를 강제할 좋은 방법이 없었죠.
00:00:09그래서 AI가 별로 원치 않는 곳에 자체 스타일을 멋대로 추가하는 것을 보셨을 겁니다.
00:00:13사람들이 StarLex 같은 대안으로 돌아선 것도 주로 이 때문이죠.
00:00:17하지만 이제 ShadCN에 이에 대한 해결책이 생겼습니다. Tailwind 디자인 시스템을 위해 구축된
00:00:21에이전트 우선(agent-first) 린터입니다. 바로 어떻게 작동하는지 살펴보죠.
00:00:29우선 이 린터가 실제로 무엇을 하는지부터 알아봅시다. Tailwind에서 클래스 이름은 단순 문자열이고,
00:00:34TypeScript는 그 이상의 제약을 두지 않습니다. 즉, 패딩을 자체적으로 제어하는 버튼에
00:00:38오버라이드를 위한 패딩을 또 넣을 수 있다는 뜻입니다. 이미 테마 색상을 사용하는 컴포넌트에
00:00:43background-pink-500 같은 임의의 색상을 추가하거나, 디자인 시스템에 12에서 16으로 이어지는
00:00:48간격 스케일이 이미 존재함에도 13픽셀 같은 임의의 패딩 값을 지정해 버릴 수도 있죠.
00:00:53현재 상황에서는 이런 것들이 코드베이스에 어떤 에러도 일으키지 않으며,
00:00:58직접 코드를 리뷰하거나, 다른 에이전트가 검토할 수 있도록 마크다운 파일에 수많은 규칙을
00:01:02추가해야만 잡아낼 수 있습니다. 하지만 마크다운은 제약 사항을 강제하는 데 그다지 훌륭하지 못합니다.
00:01:07린터만큼 엄격할 수 없죠. ShadCN은 실제로 이에 대한 수치 검증을 진행했습니다.
00:01:12에이전트가 시스템 규칙을 벗어나도록 유도하는 8개 작업을 설정했는데요, "삭제 버튼을 추가하되 디자인 요구사항에 따라 핑크색 알약 모양 모서리로 만들 것",
00:01:16"이 목업과 완전히 일치하게 13픽셀 패딩, 10픽셀 테두리 반경을 적용해 통계 카드를 제작할 것",
00:01:22또는 "요금제 카드가 눈에 띄도록 확 살려볼 것" 같은 프롬프트였습니다. 보시다시피
00:01:27모든 모델이 이러한 작업을 수행할 때 무수한 디자인 시스템 위반을 만들어냈지만, 린터를
00:01:31사용했을 때 위반 건수는 0으로 떨어졌습니다. 린터의 실효성을 확인했으니, 이제 어떻게 사용하는지 볼까요?
00:01:36이 플러그인은 Oxlint나 ESLint용이며, 모든 Tailwind V4 프로젝트에서 작동합니다.
00:01:41ShadCN UI를 꼭 쓰지 않아도 됩니다. "no restyle, but allowing layout"이라는 하나의 규칙으로 시작해 봅시다.
00:01:47이 규칙은 기본적으로 페이지에서 margin, width, flex, hidden 같은 속성으로
00:01:51컴포넌트를 배치하는 것은 허용하지만, 재스타일링은 허용하지 않는다는 뜻입니다. 즉 패딩, 색상, 타이포그래피,
00:01:56모양, 효과, 모션을 임의로 추가할 수 없습니다. 이를 어기면 다음과 같은 에러가 발생합니다. "Button에 p-4는 허용되지 않음,
00:02:02Button이 여백 권한을 소유하므로 size default나 margin을 사용하거나, 부모의 gap을 활용할 것."
00:02:07"디자인 시스템에서 명시적으로 요구하는 경우에만 컴포넌트에 크기를 추가할 것."
00:02:11이 에러 메시지의 멋진 점은 size 목록 같은 것들이 린터에 미리
00:02:15정의되어 있는 게 아니라는 사실입니다. 실제로 버튼 컴포넌트 자체에 설정된 CVA 구성을 읽어오는 것이죠.
00:02:19따라서 size를 추가하더라도 에러 메시지가 해당 선택지를 정확히 감지해냅니다. 재스타일링을 유발하는
00:02:24기본적인 모든 Tailwind 기능도 마찬가지입니다. 색상을 추가하려고 하면 컴포넌트에
00:02:29정의된 색상만 사용할 수 있다고 알려줍니다. 이런 방식의 장점은 매우 명확한 에러
00:02:33메시지를 얻을 수 있고, 에라이 에이전트가 이를 읽어 수정 방법을 단번에 이해한다는 점입니다.
00:02:37무엇이 올바른지 이해하려고 전체 디자인 마크다운 파일을 불러올 필요가 없기 때문에
00:02:41토큰 사용량도 실제로 절약할 수 있습니다. 린터가 매우 구체적인 힌트만 딱 제공해주면 되니까요.
00:02:45여기까지가 첫 번째 규칙인 no restyle의 기본적인 사용법이며, 여러분의 디자인 시스템에
00:02:50맞게 커스텀할 수 있는 옵션이 엄청나게 많습니다. 이에 대해서는 잠시 후에 다시 살펴보겠습니다. 우선
00:02:54총 6개 중 나머지 5개 규칙부터 확인해보죠. "no raw colors"는
00:02:58background-pink-500 같은 값을 직접 입력하거나, 존재하지 않거나 오타가 난 색상을
00:03:03사용하지 못하게 막는 규칙입니다. 반드시 테마에 디자인된 색상만 써야 하며, 이 역시
00:03:08허용되는 값을 직접 알려주어 에이전트를 돕습니다. 이 기능은 SVG에서도 동작하므로, path에 하드코딩된
00:03:13fill 값을 쓰려고 하면 텍스트 색상 클래스와 함께 currentcolor를 사용하라는 에러가 뜹니다. 다음은
00:03:18"no arbitrary values" 규칙인데요, 이름 그대로의 역할을 합니다. 대괄호 안에 13px를 넣은 패딩 같은 것은
00:03:24하드코딩되어 토큰이 아니라는 안내가 나오며, 대신 스케일상 동일한 값인 p-3.25를 쓰도록 유도합니다.
00:03:30마찬가지로 대괄호 안에 rounded-10px 같은 것을 넣으면 rounded-lg를 대신 사용하라는 에러가 뜨는데,
00:03:35린터가 나의 radius 토큰을 실제로 읽어서 10px에 해당하는 값이 무엇인지 알고 있기 때문이죠.
00:03:40이 규칙이 디자인 시스템을 제대로 이해하고 있음을 보여주는 가장 멋진 예시 중 하나는,
00:03:44임의의 배경색을 설정하려고 할 때 에러 메시지가 가장 가까운 테마 토큰이 무엇인지 알려준다는 점입니다.
00:03:49에이전트 입장에서 더없이 훌륭한 문맥 정보가 되어주죠. 나머지 규칙들은 직관적입니다.
00:03:54인라인 스타일을 막는 규칙, 알 수 없는 클래스 사용을 차단하는 규칙,
00:03:57그리고 정적 클래스를 강제하는 규칙이 있습니다. 이 규칙이 흥미로운 이유는, 템플릿 리터럴 내부의
00:04:02동적 값 때문에 린터가 이 클래스가 무엇이 될지 실제로 볼 수 없다는 점에 있습니다.
00:04:06따라서 린터가 구동될 수 있도록 동적 클래스 처리 방식을
00:04:09변경하도록 유도하는 규칙입니다. 여기까지가 린터의 6가지 규칙이었는데요,
00:04:13앞서 언급했듯 프로젝트의 디자인 시스템에 맞춰 조정할 수 있는
00:04:16커스텀 옵션이 다양합니다. 이때 사용하는 것이 계약(Contract) 설정입니다. 컴포넌트 이름 정규식에
00:04:21매칭되는 컴포넌트별 규칙인데요, 예시로 카드 타이틀의 타이포그래피는 변경할 수 있지만
00:04:25폰트 패밀리나 굵기는 변경할 수 없게 설정했고, 카드 컨텐츠는 여백을 바꿀 수는 있지만
00:04:30타이포그래피는 바꿀 수 없도록 해두었습니다. 이제 카드 타이틀에 text-lg를 주는 것은 에러 없이 통과되고,
00:04:36컨텐츠에 p-6을 넣는 것도 통과하지만, 타이틀의 굵기를 바꾸려고 하면 예상대로 에러가 발생합니다.
00:04:41이 설정들 중 또 다른 훌륭한 옵션은 커스텀 메시지입니다. 모든 규칙과 규칙 유형은 실제 코드로
00:04:45채워지는 플레이스홀더가 포함된 메시지를 받을 수 있습니다. 제 버튼 계약 설정을 예로 들면,
00:04:50레이아웃 설정 시 너비는 Button이 아닌 부모 컨테이너에 설정하라는 메시지를 추가했고,
00:04:55Button이 패딩 권한을 소유하므로 size 템플릿과 함께 버튼 size를 사용하라는 메시지를 설정했습니다.
00:04:59이 값들은 저의 디자인 시스템 정보로 자동 채워지게 되죠. 또한 전역 노트(global note)를 설정할 수도 있는데,
00:05:04모든 검출 내역 마지막에 해당 메시지가 덧붙여집니다. 이는 에이전트에게 추가 힌트나 문맥을
00:05:08제공하여, 공식 문서나 가이드라인의 위치를 알려주는 용도로 유용합니다. 이미 구축된
00:05:12디자인 시스템이 있다면 이 린터를 도입해 시작하는 과정에서 약간의 추가 작업이
00:05:16필요할 수 있습니다. 시스템에 맞춰 설정을 구성해야 하기 때문이죠. 하지만 대부분의 디자인 시스템을
00:05:20소화할 만큼 유연해 보이며, AI 에이전트의 도움을 받아 손쉽게 시작할 수도 있을 겁니다. 마지막으로 이야기하고 싶은 건
00:05:25이 린터가 하지 못하는 한계점입니다. 순수 CSS는 감지하지 못하기 때문에, 전역 CSS 파일이나
00:05:30@apply에 날것의 색상이 포함되어 있다면 이에 대해 규칙을 강제할 수 없습니다. 또한 부모 선택자를
00:05:36자식까지 추적하지 못하고, 파일 내에서 클래스 값을 단 1단계(one hop)까지만 추적하며,
00:05:40새로 만든 테마 토큰은 정의상 시스템 규칙을 따르는 것이 되므로, 린터 규칙을 우회하려고
00:05:46디자인 시스템에 새 색상 토큰을 멋대로 추가하는 에이전트의 행위는 통과되어 버립니다.
00:05:50결국 에이전트가 추가하는 토큰과 변형(variant)을 사람이 직접 검토해야 한다는 뜻이죠.
00:05:54린터는 규칙 위반 여부만 확인할 뿐, 새로운 주황색이 디자인 시스템에 적합한지 결정해주진 못합니다.
00:05:59검토는 여전히 여러분의 몫이지만, 이 린터가 번거로운 작업의 일부를 덜어줄 것입니다.
00:06:03도입을 고려할 때 생각해볼 또 다른 점은, 여러분의 컴포넌트 API가 강제 규칙을 적용할 만큼
00:06:08충분히 엄격한가 하는 점입니다. 버튼이 그저 임의의 클래스명을 받고 아무런 변형도 없다면
00:06:13이 린터가 제안할 수 있는 내용이 없습니다. 따라서 디자인 시스템이 구축되어 있고
00:06:18실제 변형들을 사용하고 있어야 한다는 전제가 필요합니다. 솔직히 ShadCN UI를 쓰고 계시다면
00:06:22이미 갖춰진 상태이므로 대부분 문제없을 겁니다. 마지막 아쉬운 점은 현재 Oxlint와 ESLint에서만
00:06:27사용 가능하고 Biome 플러그인은 아직 없다는 것인데, GitHub에 이슈가 등록되어 있으니 조만간 지원되길 바랍니다.
00:06:31새로 나온 ShadCN Linter는 디자인 시스템을 더 잘 준수하도록 돕는 6가지 규칙으로 구성되어 있어,
00:06:36AI가 대부분의 코드를 작성하는 시대에 큰 도움이 됩니다. 이 린터가 여러분이 겪던 문제들을
00:06:41해결해줄지, 아니면 여전히 StarLex 같은 대안으로의 전환을 고려 중이신지
00:06:45하단 댓글로 알려주세요. 구독도 부탁드리며, 늘 그렇듯 다음 영상에서 뵙겠습니다.

핵심 요약

Shadcn이 출시한 에이전트 우선 린터는 CVA와 테마 토큰을 직접 읽어 AI 에이전트의 스타일 규칙 위반을 0건으로 줄이고 디자인 시스템 준수를 자동화합니다.

하이라이트

  • Shadcn이 출시한 에이전트 우선(agent-first) 린터는 AI가 Tailwind CSS 프로젝트에서 임의로 클래스 이름을 지정하거나 스타일을 변경하는 문제를 차단합니다.

  • 테스트 프롬프트 기반 평가 결과, 린터 도입 전 모든 AI 모델이 디자인 시스템 규칙을 위반했으나 린터 적용 후 위반 건수가 0건으로 감소했습니다.

  • no restyle 규칙을 적용하면 margin, width, flex 등 배치용 속성만 허용되고 패딩, 색상, 모션 등 스타일 변경 시 상세한 에러 메시지가 출력됩니다.

  • 린터는 CVA(Class Variance Authority) 및 토큰 정의를 직접 읽어 에이전트에 가깝고 올바른 디자인 토큰 대체안을 즉각 제시합니다.

  • Oxlint 및 ESLint 환경의 Tailwind V4 프로젝트에서 작동하며, CVA 기반 스펙만 갖춰져 있다면 ShadCN UI 미사용 코드베이스에서도 활용 가능합니다.

타임라인

Tailwind CSS의 디자인 시스템 위반 문제와 Shadcn 린터 도입

  • Tailwind의 클래스 이름은 단순 문자열 형태여서 TypeScript 환경에서도 디자인 시스템 제약을 강제하기 어렵습니다.
  • 마크다운 기반 규칙 문서는 AI 에이전트의 규칙 위반을 효과적으로 막지 못합니다.
  • 8개 테스트 프롬프트를 수행한 검증 실험에서 린터 적용 후 AI의 디자인 위반 건수가 0으로 감소했습니다.

버튼 오버라이드 패딩 중복 지정, 임의의 배경색(background-pink-500) 추가, 스케일에 없는 13픽셀 패딩 할당 등은 컴파일 에러를 발생시키지 않아 코드베이스를 오염시킵니다. 마크다운 파일 형태의 지침서는 에이전트의 이탈을 완벽히 통제하지 못합니다. Shadcn의 실험 결과 린터를 결합했을 때 에이전트의 무단 스타일 변경이 완전히 차단되었습니다.

no restyle 규칙 및 CVA 연동 메커니즘

  • no restyle, but allowing layout 규칙은 배치 속성만 허용하고 스타일 재정의를 금지합니다.
  • 린터는 버튼 컴포넌트의 CVA 설정을 읽어 허용 가능한 규격을 에러 메시지에 동적으로 반영합니다.
  • 정확한 에러 메시지 힌트를 제공함으로써 AI 에이전트의 문맥 파일 참조 부담과 토큰 소비를 줄입니다.

이 규칙은 margin, width, flex, hidden 등의 레이아웃 속성을 허용하되 패딩, 색상, 타이포그래피, 모션 변경을 차단합니다. 에러 발생 시 static 목록이 아닌 컴포넌트 내부 CVA 구성을 파싱하여 대안 속성을 명시합니다. 에이전트가 전체 디자인 마크다운 문서를 읽을 필요 없이 에러 메시지만으로 즉시 수정 코드를 작성하므로 토큰 소비 절감 효과가 발생합니다.

주요 린트 규칙과 디자인 토큰 추론

  • no raw colors 규칙은 테마 외 임의 색상 및 오타를 차단하고 SVG 내 currentcolor 사용을 유도합니다.
  • no arbitrary values 규칙은 대괄호 하드코딩 값을 가장 가까운 디자인 토큰 스케일로 매핑합니다.
  • 정적 클래스 강제 규칙은 템플릿 리터럴 내 동적 값 사용을 제한하여 린터의 정적 분석을 가능하게 합니다.

no raw colors는 지정되지 않은 색상 코드 입력을 막으며 SVG path의 하드코딩 값도 감지합니다. 13px 패딩이나 10px 테두리 반경 입력 시 린터가 테마 토큰을 조회하여 p-3.25 또는 rounded-lg 같은 스케일 값을 제시합니다. 또한 템플릿 리터럴 내부의 dynamic class 생성을 제한하여 정적 분석 추적이 끊기지 않도록 보장합니다.

계약(Contract) 설정과 커스텀 메시지 확장

  • 컴포넌트 이름 정규식을 기반으로 계약(Contract)을 설정해 부분적 스타일 변경 권한을 제어합니다.
  • 플레이스홀더를 활용한 커스텀 에러 메시지와 전역 노트(global note) 추가 기능을 제공합니다.
  • 에이전트에 문서 링크나 명확한 레이아웃 규칙 가이드를 전달할 수 있습니다.

카드 타이틀은 타이포그래피 변경을 허용하고 Card Content는 여백 변경만 허용하는 식의 세부 제약 설정이 가능합니다. 에러 메시지 플레이스홀더를 활용하면 디자인 시스템 데이터가 자동으로 채워집니다. 전역 노트를 설정하면 모든 검출 내역 하단에 특정 가이드라인 문서 위치나 힌트가 첨부됩니다.

린터의 한계점과 도입 조건

  • 순수 CSS, @apply, 부모-자식 관계 추적 및 multi-hop 클래스 추적에는 제한이 있습니다.
  • 에이전트가 새롭게 정의하는 신규 색상 토큰 및 변형(variant)은 사람이 직접 검토해야 합니다.
  • 컴포넌트 API 및 변형 체계가 엄격하게 구축된 프로젝트에서만 정상 작동합니다.

린터는 정적 타일윈드 클래스 기반 검사 도구이므로 전역 CSS 파일이나 @apply 구문 내부의 날것 값은 감지하지 못합니다. 에이전트가 규칙을 우회하기 위해 새로운 토큰을 생성하는 경우 이를 통과시키므로 인간의 코드 리뷰가 병행되어야 합니다. 또한 버튼 등 기본 컴포넌트에 CVA 기반 변형 구조가 설정되어 있어야 린터가 권장 대안을 제시할 수 있습니다.

커뮤니티 글

아직 글이 없습니다. 이 영상에 대한 첫 번째 글을 작성해 보세요!

이 영상에 대해 글쓰기