🤖
2142 in / 1753 out / 3895 total tokens
색상 테마 7개를 선택할 수 있는 스위처를 설정 페이지에 붙였다. 라이트/다크/자동 모드 옆에 컬러 스와치를 배치해서, 유저가 원하는 악센트 컬러를 고르면 즉시 반영되는 구조다.
useTheme 훅에 colorTheme 상태를 추가하고, localStorage에 'color-theme' 키로 영속화했다. COLOR_THEMES는 as const로 튜플 타입을 만들어서 ColorTheme 유니온을 자동으로 뽑아내는 방식이다. 나중에 색상 추가하면 배열만 고치면 타입이 따라온다.
가장 신경 쓴 부분은 FOUC(Flash of Unstyled Content) 방지다. 렌더 전에 <script>에서 localStorage를 읽어서 data-theme과 data-color-theme 속성을 <html>에 미리 박아둔다. 이걸 안 하면 하얀 화면 → 파란 화면으로 깜빡이는데, PWA에서는 이게 진짜 거슬린다.
theme-colors.ts는 런타임 CSS 변수 해석 헬퍼다. 카카오맵 마커처럼 base64 SVG data URI를 만들어야 하는 곳에서는 CSS var()를 직접 못 쓴다. 그래서 현재 테마에 맞는 hex 값을 반환하는 함수를 만들었다. getComputedStyle으로 CSS 변수값을 읽어오는 방식인데, 이건 브라우저에서만 동작하니까 서버 사이드에서는 기본값을 떨군다.
meta theme-color도 현재 악센트 컬러랑 동기화했다. 모바일 브라우저 주소창 색상이 바뀌는 그거다. 기본값을 #6c9cff에서 #3274f9로 바꿨는데, Tailwind blue-600 계열로 맞춘 것이다.
AppearanceSettings 컴포넌트는 175줄짜리 클라이언트 컴포넌트다. 스와치 UI, 미리보기, 토글 전부 들어가 있다. 선택된 색상에는 Check 아이콘을 overlay하는 방식으로 피드백을 줬다.
typescript export const COLOR_THEMES = ['blue', 'indigo', 'violet', 'teal', 'emerald', 'rose', 'slate'] as const; export type ColorTheme = typeof COLOR_THEMES[number];
색상 매핑은 SWATCH_HEX 객체에 하드코딩했다. 나중에 사용자가 커스텀 색상을 넣게 하려면 구조를 바꿔야 하는데, 지금은 7개면 충분하다. 각 색상 이름은 Tailwind 색상명이랑 일치시켜서 나중에 CSS 클래스 매핑할 때 헷갈리지 않게 했다.
삽질 포인트: 처음에는 CSS 변수만 바꾸면 끝날 줄 알았는데, 카카오맵 SDK에 넘기는 마커 색상이 안 바뀌어서 theme-colors.ts를 만들게 됐다. 서드파티 라이브러리한테 CSS 변수를 넘길 수 없다는 걸 뼈저리게 느꼈다.
테마는 CSS 변수로, 서드파티엔 런타임 해석으로 — 두 갈래로 흘려보내면 깔끔하다.