69.1 아이콘 색이 다크 모드에서 왜 안 변해요?
색상 토큰으로 텍스트와 배경은 예쁘게 뒤집었는데, 딱 한 군데가 안 따라오는 경험 다들 하실 거예요. 바로 아이콘이에요. 다크 모드로 넘겼더니 배경은 까매졌는데 돋보기 아이콘만 시커먼 채로 남아, 어두운 배경에 파묻혀 안 보이는 거죠. 저도 이거 처음엔 원인을 못 찾았어요.
범인은 SVG 안에 박힌 색이었어요. 아이콘 파일을 열어보니 fill="#333"처럼 색이 직접 적혀 있더라고요. SVG(선과 도형으로 그린 벡터 이미지)가 제 색을 스스로 들고 있으니, 바깥에서 테마를 아무리 바꿔도 꿈쩍을 안 하는 거예요. 앞 편에서 배운 하드코딩된 색이 이번엔 이미지 속에 숨어 있던 셈이죠.
상황: SVG가 자기 색을 고정으로 들고 있으면 테마를 못 따라와요.
<svg viewBox="0 0 24 24">
<path fill="#333" d="..." /> <!-- 색이 박혀 있음 -->
</svg>
이 fill="#333" 하나 때문에 아이콘이 테마 밖으로 튕겨 나간 거예요. 해법은 아이콘한테서 색을 빼앗는 거예요. 스스로 색을 정하지 말고, 바깥이 시키는 색을 따르게 만드는 거죠. 그 마법의 키워드가 다음에 나올 currentColor예요.
69.2 currentColor는 대체 뭐예요?
currentColor는 CSS의 특별한 값인데, 뜻은 딱 하나예요. "지금 이 요소의 글자색(color)을 그대로 따라라." SVG의 fill을 currentColor로 바꿔두면, 아이콘은 자기 색을 버리고 부모가 정한 글자색을 입어요.
상황: 아이콘이 자기 색을 버리고 부모의 글자색을 따라가게 해요.
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="..." />
</svg>
.icon-button {
color: var(--color-text); /* 아이콘이 이 색을 따라옴 */
}
이제 무슨 일이 벌어지냐면요. 버튼의 color가 앞 편에서 만든 --color-text 토큰을 따르니까, 라이트에서는 아이콘이 어둡게, 다크에서는 밝게 저절로 바뀌어요. 아이콘 파일은 단 한 개인데, 테마에 맞춰 색이 알아서 따라오는 거죠. 텍스트 옆에 아이콘을 두면 글자와 똑같은 색으로 딱 맞춰지는 것도 공짜로 얻어요.
그래서 저는 아이콘을 만들 때 규칙을 하나 세웠어요. SVG 안에 색을 절대 박지 않는다. fill은 무조건 currentColor로 두고, 색은 바깥 CSS에서만 정한다고요. 이러면 아이콘 하나로 모든 테마, 모든 위치를 커버해요. 성공 표시엔 초록 글자색 옆에 두고, 경고엔 빨강 옆에 두면 같은 아이콘이 상황 색을 입어요.
69.3 사진은 테마별로 어떻게 바꿔요?
아이콘은 currentColor로 끝나는데, 사진은 얘기가 달라요. 실제 촬영한 일러스트나 스크린샷은 색을 CSS로 갈아끼울 수가 없잖아요. 밝은 배경에 그려진 그림을 다크 모드에 그대로 두면 그림 주변만 하얗게 떠서 눈이 아파요.
이건 아예 다른 이미지 두 장을 준비하고, 테마에 맞는 걸 브라우저가 골라 쓰게 하는 게 정석이에요. 이때 <picture> 태그와 미디어 조건을 써요. picture는 여러 후보 중 조건에 맞는 하나를 브라우저가 고르게 해주는 태그예요.
상황: 다크를 선호하면 어두운 그림, 아니면 밝은 그림을 골라 써요.
<picture>
<source srcset="hero-dark.webp"
media="(prefers-color-scheme: dark)" />
<img src="hero-light.webp" alt="소개 그림" />
</picture>
여기서 prefers-color-scheme는 "사용자가 다크 모드를 선호하나"를 알려주는 조건이에요. 다크를 쓰면 브라우저가 source의 hero-dark.webp를 고르고, 아니면 밑의 img의 hero-light.webp로 떨어져요. 맨 아래 img는 항상 있어야 해요. 조건에 아무것도 안 맞아도 기본으로 보여줄 그림이자, 옛 브라우저의 안전망이거든요. alt도 잊지 마세요. 그림이 안 떠도, 화면을 못 보는 분에게도 설명이 남아야 하니까요.
69.4 로고가 다크 배경에서 안 보여요
이 함정은 제가 실제로 배포까지 하고 데인 거예요. 회사 로고가 진한 남색 글씨였는데, 다크 모드에서 헤더 배경도 진한 남색이라 로고가 배경에 먹혀 안 보였어요. 사용자가 "위에 아무것도 없는데요?" 하고 문의를 준 뒤에야 알았죠.
로고는 브랜드 그 자체라 함부로 색을 바꿀 수 없어요. 그래서 라이트용, 다크용 두 벌을 따로 두는 게 정석이에요. 로고 SVG는 currentColor로 뭉개면 안 되니까요. 방식은 69.3과 똑같이 picture로 갈아끼우면 돼요.
상황: 배경에 안 묻히도록 테마별 로고 두 벌을 준비해 골라 써요.
<picture>
<source srcset="logo-white.svg"
media="(prefers-color-scheme: dark)" />
<img src="logo-navy.svg" alt="회사 로고" />
</picture>
다크 배경엔 흰색 로고, 라이트 배경엔 남색 로고가 떠서 어느 쪽이든 또렷하게 보여요. 여기서 한 가지 더 챙길 게 있어요. 우리 사이트가 직접 만든 테마 토글(버튼으로 다크를 켜는 기능)을 쓴다면, prefers-color-scheme만으론 부족해요. 사용자가 버튼으로 고른 테마는 앞 편의 data-theme로 표시되니까, CSS의 배경 이미지 방식이면 그 선택까지 같이 반영되도록 맞춰줘야 해요.
69.5 이미지 한 장으로 버티는 방법도 있어요?
두 벌을 만들기 부담스러운 경우도 있어요. 아이콘이 수백 개라 다 두 벌씩 뽑기 벅차거나, 외부에서 받아온 이미지라 원본을 못 건드릴 때요. 그럴 땐 이미지 한 장을 CSS로 살짝 손보는 우회로가 있어요. 정공법은 아니지만 알아두면 요긴해요.
대표적인 게 filter예요. 단색 아이콘이라면 다크에서 invert(밝기 반전)로 색을 뒤집어 밝게 만들 수 있어요. 사진이 다크에서 너무 쨍하면 brightness나 opacity를 살짝 낮춰 눈부심을 줄이기도 하고요.
상황: 다크에서만 단색 아이콘의 밝기를 반전시켜 밝게 보이게 해요.
[data-theme="dark"] .mono-icon {
filter: invert(1); /* 검정을 흰색으로 */
}
[data-theme="dark"] .photo {
filter: brightness(0.85); /* 눈부심 완화 */
}
다만 주의가 있어요. filter는 색을 정확히 통제하기 어려워요. invert는 브랜드 남색을 이상한 주황으로 뒤집어버릴 수 있어서, 흑백 단색에나 안전해요. 그래서 저는 순서를 이렇게 잡아요. 아이콘은 currentColor가 1순위, 사진과 로고는 picture로 두 벌이 정석, filter는 두 벌을 못 만들 때의 임시방편. 급할 때 filter로 때우더라도, 중요한 이미지는 결국 제대로 된 두 벌로 돌아오는 게 좋아요.
69.6 오늘 이미지와 테마를 정리해볼게요
테마가 텍스트에서 끝나지 않고 이미지까지 이어지는 지점을 훑었어요. 종류마다 정석이 달라요.
아이콘은 SVG에 색을 박지 말고 fill="currentColor"로 둬서, 바깥 글자색을 따라 저절로 바뀌게 하세요. 사진과 로고는 색을 갈아끼울 수 없으니 테마별 두 벌을 만들고, <picture>와 prefers-color-scheme로 브라우저가 맞는 걸 고르게 하세요. 두 벌이 부담되면 filter로 임시 대응하되, 흑백이 아니면 색이 틀어질 수 있으니 조심하고요.
이 셋을 관통하는 원칙은 앞 편과 똑같아요. 이미지도 테마의 일부로 설계한다. 초보 때는 텍스트 색만 뒤집고 "다크 모드 끝!"이라 외치지만, 진짜 완성도는 파묻힌 아이콘, 하얗게 뜬 그림, 안 보이는 로고 같은 이미지의 빈틈에서 갈려요. 화면 안의 모든 색이 텍스트든 아이콘이든 사진이든 테마를 함께 따라오게 만들면, 그제야 다크 모드가 어색한 데 없이 딱 맞아떨어져요.