지금 프론트엔드 스타일링은 전환점 위에 있다
styled-components가 처음 등장했을 때의 흥분을 기억한다. 컴포넌트와 스타일이 하나의 파일 안에서 공존하고, props 기반의 동적 스타일링이 가능해졌다. 그 경험은 분명 혁신이었다. 하지만 2026년의 React 생태계는 그 혁신이 만들어낸 기술 부채를 정면으로 마주하고 있다.
dev.to에 게재된 Wilson Xu의 분석은 이 상황을 직설적으로 정리한다. "CSS-in-JS는 Flash처럼 죽은 게 아니라 jQuery처럼 죽었다." 수백만 사이트에서 여전히 돌아가고 있지만, 새 프로젝트의 기본값이 되어서는 안 된다는 뜻이다.
왜 Runtime CSS-in-JS는 RSC와 함께할 수 없나
핵심 문제는 단순하다. Runtime CSS-in-JS는 스타일을 브라우저에서, JavaScript 실행 시점에 생성한다. 이 구조는 React Server Components와 근본적으로 충돌한다.
styled-components는 스타일 주입을 위해 React context에 의존하는데, Server Components에는 context가 없다. styled.div를 RSC 안에서 쓰는 순간 에러다. 스트리밍 SSR 환경에서도 문제가 된다. React의 스트리밍 렌더러는 아직 렌더링되지 않은 컴포넌트의 스타일을 미리 알 수 없기 때문에, CSS-in-JS 라이브러리가 요구하는 "모든 스타일을 사전에 파악" 방식과 충돌한다.
성능 수치도 냉정하다. 중급 안드로이드 기기(Moto G Power 기준) 실측에서 styled-components는 번들 파싱과 스타일 주입을 합쳐 약 20ms의 추가 비용을 발생시킨다. CSS Modules나 Vanilla Extract는 런타임 비용이 0이다. 20ms가 작아 보일 수 있지만, 이것이 모든 라우트 이동마다 반복된다는 게 문제의 본질이다.
두 가지 대안, 각자의 자리가 있다
현실적인 대안은 두 갈래다.
CSS Modules는 가장 보수적이고 안전한 선택이다. Pages Router, App Router, Server Components, 엣지 함수 — 어디서든 작동한다. 빌드 타임에 스코프가 결정되는 순수 CSS다. 동적 스타일이 필요할 때는 CSS Custom Properties를 조합한다. style={{ '--btn-color': customColor }}처럼 인라인으로 CSS 변수를 주입하고, CSS에서 var(--btn-color, var(--color-primary))로 폴백을 정의하는 패턴이다. Wilson Xu의 분석에 따르면 이 방식이 styled-components로 구현하던 것의 95%를 커버한다.
Vanilla Extract는 한 단계 더 나아간다. TypeScript로 스타일을 작성하고, 빌드 타임에 정적 .css 파일로 추출된다. 런타임 비용 제로에 TypeScript 타입 안전성까지 얻는다. recipe() API로 버튼의 variant, size, loading 상태 조합을 선언하면, 존재하지 않는 variant 값을 넘기는 순간 컴파일 에러가 발생한다. 런타임이 아니라 개발 시점에 실수를 잡는다.
무엇보다 Vanilla Extract의 진가는 테마 시스템에 있다. createThemeContract()로 필요한 토큰의 형태를 계약으로 정의하고, 라이트/다크 테마가 그 계약을 각자 구현한다. 두 테마 모두 정적 CSS다. 클래스 교체만으로 테마 전환이 완성되고, JavaScript는 개입하지 않는다. 디자인 시스템을 구축하는 관점에서 이보다 타이트한 구조를 찾기 어렵다.
접근성: AI가 바꾼 것과 바꾸지 못한 것
스타일링 패러다임의 전환과 함께, 접근성 구현 방식에도 의미 있는 변화가 있었다. grossbyte.io에서 공유된 사례가 인상적이다. 수개월째 미뤄온 접근성 작업을 Claude Code CLI를 활용해 2시간 만에 완료했다는 내용이다.
결과물은 WCAG 2.1 Level AA를 넘어서는 수준이었다. Semantic HTML과 ARIA 패턴 적용, 포커스 트랩, prefers-contrast, prefers-reduced-motion, forced-colors 미디어 쿼리 대응, aria-live 기반의 접근 가능한 폼 구현까지. 디스클로저 위젯이나 라이브 리전 같은 비자명한 ARIA 패턴도 올바르게 구현되었다.
중요한 건 이 사례에서 드러나는 AI의 역할 구분이다. AI가 잘하는 것은 "구현"이다. 수십 개 파일에 걸쳐 레이블을 추가하고, CSS 변수를 조정하고, role 속성을 붙이는 반복적인 작업. 이것이 이틀이 아닌 2시간으로 줄어들었다. 하지만 aria-live가 왜 필요한지, 포커스 트랩이 언제 적용되어야 하는지, 생성된 contrast 값이 맥락에 맞는지 판단하는 건 여전히 사람의 몫이다. AI는 접근성 전문가를 대체하지 않는다. 다만 접근성 구현의 진입 장벽을 극적으로 낮춘다.
RSC와 스타일링·접근성의 교차점
이 두 흐름을 RSC 아키텍처 위에 겹쳐보면 하나의 설계 원칙이 보인다. 런타임 비용은 서버에서 흡수하고, 클라이언트에는 최소한의 정적 결과물만 전달하라.
CSS Modules와 Vanilla Extract는 이 원칙을 스타일링에서 실현한다. 빌드 타임에 결정된 정적 CSS가 서버에서 렌더링되고, 클라이언트는 클래스 이름만 적용하면 된다. RSC가 JavaScript 번들에서 서버 코드를 걷어내듯, 정적 CSS는 스타일 연산을 클라이언트에서 걷어낸다.
접근성 역시 같은 맥락에서 바라볼 수 있다. Semantic HTML과 ARIA 속성은 대부분 Server Components 레이어에서 정적으로 렌더링된다. aria-label, role, aria-describedby 같은 속성들은 JavaScript가 필요하지 않다. 동적으로 변하는 상태(aria-live, aria-busy)만 Client Component 영역으로 분리하면 된다. 이는 RSC의 "서버/클라이언트 경계를 의도적으로 설계하라"는 원칙과 정확히 일치한다.
실무 적용 시사점
새 프로젝트라면 선택은 단순하다. Next.js App Router 기반이라면 CSS Modules를 기본으로, 디자인 시스템을 타이트하게 관리해야 하는 상황이라면 Vanilla Extract를 추가하는 조합이 현실적이다. Tailwind CSS와 shadcn/ui를 이미 쓰고 있다면 이 역시 정적 CSS 방식이므로 RSC와 궁합이 좋다.
기존 프로젝트의 마이그레이션은 점진적으로 접근해야 한다. Wilson Xu의 마이그레이션 가이드처럼 "정적 스타일 → 테마 의존 → Props 기반 동적 → 복잡한 애니메이션" 순서로 분류하고, 컴포넌트 단위로 교체하는 것이 안전하다.
접근성은 더 이상 "나중에" 의 작업이 될 수 없다. AI를 활용하면 구현 비용이 현격히 낮아진 만큼, 프로토타이핑 단계부터 ARIA 패턴과 키보드 내비게이션을 포함시키는 것이 가능해졌다.
전망: 정적이 새로운 기본값이 된다
"런타임 CSS-in-JS는 현대 React 앱의 크리티컬 패스에 속하지 않는다" — Vercel, Shopify, Linear 같은 팀들이 마이그레이션 노트에 명시하는 문장이다. 이건 취향의 문제가 아니라 아키텍처의 방향이다.
React Server Components가 서버/클라이언트 경계를 명시적으로 관리하게 만든 것처럼, 스타일링과 접근성 역시 "어떤 계층에서, 언제 처리되는가"를 의식적으로 설계해야 하는 시대가 됐다. 빠른 프로토타이핑에는 AI를, 경계 설계에는 아키텍처 원칙을, 검증에는 사용자를 — 이 세 가지를 순환시키는 흐름이 결국 더 빠르고 더 접근 가능한 제품을 만드는 경로다.