AI가 다크모드 CSS를 틀리게 짜는 이유—Design Token 명세가 먼저다

AI가 다크모드 CSS를 틀리게 짜는 이유—Design Token 명세가 먼저다

컴포넌트마다 라이트/다크를 따로 구현하는 AI의 패턴은 훈련 데이터의 반영일 뿐—올바른 아키텍처는 토큰 명세가 먼저 존재해야 비로소 가능하다.

Design Token 다크모드 CSS AI 코딩 에이전트 시맨틱 토큰 CSS 아키텍처 Cursor 테마 설계 컴포넌트 전략
광고

Cursor나 Claude Code에게 "다크모드를 추가해줘"라고 요청하면 높은 확률로 dark.css가 생성된다. styles.css가 라이트 테마를 담고, dark.css가 컴포넌트별 오버라이드를 늘어놓는 구조다. 처음엔 그럴듯해 보인다. 하지만 이 패턴은 본질적으로 같은 UI를 두 벌 구현하는 것이며, 프로젝트가 커질수록 두 파일은 서로 다른 방향으로 드리프트하기 시작한다.

문제의 핵심은 AI가 잘못 배웠다는 게 아니다. 오히려 AI는 인터넷에서 가장 많이 등장하는 패턴을 충실하게 재현한다. 그리고 웹의 수많은 튜토리얼은 다크모드를 .dark .card { background: #0f172a; } 식의 컴포넌트 셀렉터 오버라이드로 가르쳐왔다. dev.to의 분석("Why Dark Mode Should Not Be a Second CSS File")이 정확히 짚었듯, 테마 로직이 컴포넌트 셀렉터에 분산된 순간 모든 인터랙션 상태—hover, focus, disabled, pressed—가 각각 두 벌씩 관리돼야 한다. 버튼 하나에 포커스 링을 추가했다면, 누군가 다크 테마 파일에도 같은 수정을 기억해서 해줘야 한다. 시스템이 개발자의 기억력에 의존하는 순간, 버그는 시간 문제다.

올바른 접근은 컴포넌트를 테마에 무지하게 만드는 것이다. 컴포넌트는 var(--color-surface), var(--color-primary), var(--state-primary-hover) 같은 시맨틱 토큰만 참조한다. 실제 색상값은 :root:root[data-theme="dark"] 블록에서 토큰 이름을 유지한 채 값만 교체된다. 이 구조에서 다크모드는 "두 번째 CSS 파일"이 아니라 같은 계약(contract)에 다른 값 세트를 주입하는 것이다. 카드도, 버튼도, 인풋도 코드를 바꿀 필요가 없다. 테마 레이어만 교체된다.

여기서 AI의 구조적 한계가 선명하게 드러난다. 같은 맥락에서 Cursor가 보안 토큰 생성에 Math.random()을 반복적으로 사용하는 이유("Why Cursor Keeps Using Math.random() for Session Tokens")와 동일한 메커니즘이 작동한다. AI는 "랜덤 문자열 생성"이라는 요청에 가장 자주 등장한 패턴을 재현하고, 그 패턴이 UI 키용인지 보안 토큰용인지 구분하지 못한다. 다크모드도 마찬가지다. AI는 "다크모드 추가"라는 요청이 Design Token 기반 아키텍처를 전제로 해야 하는지, 단순 오버라이드 파일로 해결해도 되는지를 스스로 판단하지 않는다. 컨텍스트에 아키텍처 명세가 없으면, 가장 흔한 패턴이 선택될 뿐이다.

에이전트 코딩 패턴의 확장성 연구("Which Agentic Coding Patterns Actually Scale")가 제시한 핵심 인사이트가 여기서도 그대로 적용된다. 스펙이 먼저 존재하지 않으면, 에이전트는 설계 의도가 아닌 구현 관성을 따른다. spec.md에 "이 프로젝트의 모든 색상은 시맨틱 토큰으로만 참조되어야 한다. 컴포넌트 셀렉터에 직접 색상값이나 테마 조건부 로직이 들어가서는 안 된다"라는 명세 한 줄이 없다면, Cursor는 오늘도 dark.css를 생성할 것이다. 토큰 명세는 디자인 시스템의 문서가 아니라 에이전트 제어의 게이트다.

실무 적용 관점에서 몇 가지 원칙을 정리할 수 있다. 첫째, 토큰은 역할을 서술해야 한다. --dark-card-background가 아니라 --color-surface다. 컴포넌트가 활성 테마를 인식하는 순간 아키텍처는 무너진다. 둘째, 전경색과 배경색은 쌍으로 정의해야 한다. --color-primary: #60a5fa(다크 테마에서 밝은 파란색)로만 바꾸고 텍스트를 여전히 color: white로 하드코딩하면 대비율이 깨진다. --color-on-primary가 반드시 함께 정의돼야 한다. 셋째, 인터랙션 상태도 토큰의 영역이다. hover, focus, disabled 모두 --state-primary-hover 형태의 토큰으로 추상화되어야 다크 테마에서 컴포넌트 오버라이드 없이 값만 교체할 수 있다. 넷째, 스페이싱·타이포그래피·보더 래디어스는 보통 테마 간에 바뀌지 않으므로 토큰 분리 대상이 아니다. 색상과 인터랙션 상태에 집중하면 된다.

전망은 분명하다. AI 코딩 에이전트가 CSS 생성 품질을 자체적으로 높이기를 기다리는 건 비효율적인 전략이다. 에이전트는 명세 없이는 관성을 따른다. 반대로 Design Token 레이어를 먼저 설계하고, 토큰 명세와 사용 규칙을 .cursor/rules 또는 SPEC.md에 명시적으로 작성해두면—에이전트는 그 계약 위에서 훨씬 일관된 코드를 생산한다. 다크모드 CSS 아키텍처의 문제는 AI의 한계가 아니라 명세의 부재다. 디자인 토큰을 먼저 정의하는 것, 그게 AI 협업 시대의 프론트엔드 아키텍트가 가져야 할 첫 번째 습관이다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요