CSS 수작업을 시스템으로 대체하는 UI 개발 전략

CSS 수작업을 시스템으로 대체하는 UI 개발 전략

다크 모드 자동 감지부터 키프레임 자동 생성까지—반복 코드를 구조로 흡수하면 컴포넌트는 비로소 '관리 가능'해진다

CSS 다크 모드 prefers-color-scheme Storybook 테마 FSCSS 디자인 시스템 CSS 애니메이션 CSS 변수
광고

손으로 쓴 CSS, 얼마나 오래 버틸 수 있을까

프론트엔드 개발에서 CSS는 늘 '일단 동작하면 끝'으로 다뤄지기 쉬운 영역이다. 다크 모드를 추가할 때마다 컴포넌트마다 색상 값을 덮어쓰고, 로딩 인디케이터 하나 만들겠다고 @keyframes에 0%부터 100%까지 101줄을 손으로 채운다. 당장은 돌아가지만, 6개월 뒤 요구사항이 바뀌는 순간 그 코드는 조용히 빚이 된다. 최근 두 가지 흐름이 이 문제를 정면으로 겨냥하고 있다. 하나는 CSS 변수와 미디어 쿼리를 조합한 테마 시스템이고, 다른 하나는 키프레임 자동 생성 전처리기다.

다크 모드, 'JS가 켜주는 것'에서 'CSS가 아는 것'으로

velog에 공유된 CSS 다크 모드 자동 감지 구현 사례는 흔히 놓치는 설계 포인트를 짚는다. 많은 팀이 [data-theme="dark"] 셀렉터로 다크 모드를 구현하지만, 이 방식은 JS가 data-theme 속성을 직접 세팅해줘야만 작동한다. 사용자가 OS를 다크 모드로 설정해도 JS가 개입하기 전까지 화면은 라이트 모드로 깜빡인다—이른바 FOUC(Flash of Unstyled Content) 문제다.

해법은 prefers-color-scheme 미디어 쿼리와 data-theme 패턴을 동시에 운용하되, 셀렉터를 :root:not([data-theme="light"])로 구성하는 것이다. 이 한 줄이 세 가지 상황을 커버한다. OS가 다크 모드이고 data-theme가 없으면 자동으로 다크 테마가 적용되고, 사용자가 명시적으로 라이트 모드를 선택했다면(data-theme="light") 강제로 유지된다. JS 토글로 data-theme="dark"를 세팅하면 기존 셀렉터가 그대로 받아낸다. CSS 변수 블록 두 개(미디어 쿼리용·명시 토글용)에 동일한 값을 선언하는 것이 약간 중복처럼 보이지만, 이 중복이 오히려 두 진입점을 명확히 분리해 유지보수를 쉽게 만든다.

Storybook 연동: 디자인 시스템의 '진실의 원천'을 지키는 방법

테마 시스템을 구축했다면 다음 질문은 자연스럽게 따라온다. "컴포넌트가 두 테마에서 모두 올바르게 보이는지 어떻게 검증하지?" Storybook의 @storybook/addon-themes가 이 지점을 채운다. withThemeByDataAttribute 데코레이터를 preview.ts에 등록하면 툴바에 Light/Dark 토글이 생기고, 선택값이 story iframe의 <html> 요소에 data-theme 속성으로 자동 주입된다. 토큰 CSS의 [data-theme="dark"] 셀렉터가 그대로 반응하니 별도 Storybook용 테마 코드를 따로 관리할 필요가 없다.

한 가지 주의할 점이 있다. Storybook의 docs 모드에서는 Storybook 자체 스타일이 배경색을 덮어쓰기 때문에 preview.css에서 !important로 오버라이드가 필요하다. 그리고 사이드바·툴바 영역(manager)은 story iframe과 완전히 분리된 컨텍스트이므로, manager.ts에서 window.matchMedia로 OS 테마를 별도로 감지해 addons.setConfig를 호출해야 한다. 이 두 영역의 분리를 이해하지 못하면 "왜 docs 배경만 흰색이지?"라는 당혹스러운 버그를 한참 들여다보게 된다.

키프레임 101줄, 이제 5줄로

다크 모드 시스템이 '테마 관리의 수작업'을 제거한다면, dev.to에서 소개된 FSCSS(Figured Shorthand CSS)는 '애니메이션 코드의 수작업'을 겨냥한다. 문제는 단순하지만 뼈아프다. 0%부터 100%까지 숫자를 세는 프로그레스 바 애니메이션을 순수 CSS로 만들려면 @keyframes 안에 101개 키프레임을 일일이 써야 한다. PR에서 이 파일을 리뷰하는 동료의 표정을 상상해보라.

FSCSS의 접근은 명쾌하다. @arr로 값 배열이나 범위를 선언하고, f-each 구문으로 @keyframes 안에서 순회하면, 컴파일러가 퍼센트 계산을 포함한 완전한 CSS를 뱉어낸다. 100단계 카운터가 ~5줄로 표현되고, 범위를 500으로 늘리거나 10 단위 스텝으로 바꾸는 것은 숫자 하나만 고치면 끝이다. 출력은 순수 CSS이므로 런타임 성능은 브라우저 렌더링 엔진이 그대로 담당한다. JS 스레드를 건드리지 않으니 레이아웃 스래싱 우려도 없다. 생성된 CSS가 수백 개의 키프레임으로 커질 수 있지만, 반복 텍스트는 Gzip 압축률이 극히 높아 전송 비용은 생각보다 낮다.

두 흐름이 가리키는 같은 방향

표면적으로 다크 모드 시스템과 CSS 애니메이션 전처리기는 다른 문제를 다룬다. 그런데 두 접근이 공유하는 원칙은 동일하다. 반복되는 CSS 결정을 한 곳에서 선언하고, 나머지는 시스템이 계산하게 한다. 테마 시스템에서는 CSS 변수가 그 '한 곳'이고, FSCSS에서는 @arr 선언이 그 역할을 맡는다. 어느 쪽이든 컴포넌트 코드는 테마 변경을 모르고, 애니메이션 로직은 값 개수 변화를 모른다.

이것이 단순한 코드 절약 이상인 이유는 변경 비용의 구조적 차이 때문이다. 수작업 CSS는 변경이 발생할 때마다 영향 범위가 퍼져나간다. 시스템 기반 CSS는 선언 한 줄을 바꾸면 나머지가 따라온다. 유지보수 비용의 차이는 프로젝트 초기엔 미미하지만, 6개월·12개월 뒤에는 팀의 체감 속도 자체가 달라진다.

시사점: 지금 당장 점검할 세 가지

이 두 흐름을 실무에 연결한다면 세 가지 체크포인트가 유효하다. 첫째, 현재 프로젝트의 다크 모드가 JS 의존적이라면 prefers-color-scheme + :root:not([data-theme="light"]) 패턴으로 CSS 레이어를 먼저 확보하는 것을 고려할 만하다. FOUC 제거는 성능 지표보다 체감 품질에 더 직접적인 영향을 준다. 둘째, 디자인 시스템을 Storybook으로 관리하고 있다면 addon-themes로 테마 검증을 자동화 루틴에 포함시킬 수 있다. 수동으로 배경색을 바꿔가며 확인하는 시간을 없앨 수 있다. 셋째, 규칙적인 패턴의 @keyframes가 코드베이스에 있다면 FSCSS를 파일럿으로 시도해볼 만하다. Sass와 충돌하지 않고, 출력이 표준 CSS이므로 도입 리스크가 낮다.

전망: CSS 툴링의 '좁고 깊은' 전문화

FSCSS가 흥미로운 이유는 Sass처럼 CSS 전반을 프로그래밍 언어화하려 하지 않기 때문이다. 키프레임 생성이라는 단 하나의 고통 포인트만 파고든다. 이 '좁고 깊은' 툴링 전략은 앞으로 더 늘어날 것으로 보인다. 번들러와 빌드 파이프라인이 성숙하면서, 특정 문제 하나만 탁월하게 해결하는 작은 도구들이 조합되는 방식으로 CSS 워크플로우가 진화하고 있다. 다크 모드 시스템처럼 브라우저 표준(prefers-color-scheme)이 성숙해지는 방향, 그리고 FSCSS처럼 전처리 레이어가 정교해지는 방향—두 벡터가 함께 작동할 때, CSS는 '일단 동작하는 코드'가 아니라 '설계된 시스템'에 가까워진다.

출처

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