custom theme 이 필요하겠다는 아이디어는 프로젝트를 처음 구상했을 때부터 존재했었다. 웹 어플리케이션 자체가 교통약자들이 편하게 갈만한 곳을 찾아볼 수 있는 어플리케이션을 만들자는 취지로 시작했으니 웹 어플리케이션을 이용할 이용자 중에 시각장애를 가진 이용자가 있을 수 있겠다는 예상도 당연히 할 수 있었고 이들이 Baple 서비스를 이용할때 조금이라도 편리하게 이용할 수 있게 하면 좋겠다는 생각이 있었기 때문이다.
초기 구상 단계에서는 구체적이게 무엇을 어떻게 구현할지는 정해지지 않았었으나 팀 회의 끝에 색맹(color-blind)인 사람들이 이용하는데 불편함이 없도록 어플리케이션 내에서 사용하는 컬러들을 변경하는 theme 을 만들자는 것으로 합의가 되었다.
막상 그렇게 정해지고 나서도 구체적이게 색맹인 사람들이 어떤것을 요구하는지 아는바가 없어서 한동안 여러 자료들을 찾아보았다.
그래서 알게 된 사실들은 다음과 같다.
- 색맹은 단순히 세상이 흑백으로 보이는 것이 아니다. 다양한 종류의 색맹이 존재한다.
- 가장 많은 것은 '녹색맹' 이며 그 다음으로 '적록색맹'이 많다.
- 명도와 채도를 낮추어도 잘 보이는 색을 활용 하는 것이 좋다.
- 고대비를 활용 하는 것이 유리하다.
위 사실을 기반으로 색맹중 가장 많은 비중을 차지하는 녹색맹과 적록색맹에 초점을 두면서 최대한 많은 종류의 색맹 환자들이 인식할 수 있는 색을 찾아보기로 한다.
위 사진들은 다양한 종류의 색맹 환자들에게 식별 되는 색상을 표현한 자료이다. 그중 '파란색' 에 주목하기로 했다. 거의 모든 종류의 색맹 환자들에게 식별이 가능한 색상이었기 때문이다.
마침 디자이너님이 정해준 컬러세트 중에 적절해 보이는 파란색이 있었다.
#66b6ff
바로 이 색이다. 이 색을 활용하여 색맹 theme 을 만들기로 결정하였다.
이전에 MVP 버전을 만들면서 theme 을 적용할 기본적인 준비는 해둔 상태였다. 그 준비라 함은 아래와 같다.
//provider.tsx
(전략)
return (
<QueryClientProvider client={queryClient}>
<NextUIProvider>
<NextThemesProvider
attribute='class'
defaultTheme='baple'
//TODO: light, dark 대신 low-vision 이나 high-contrast 같은 theme 만들기
themes={['light', 'dark', 'baple', 'color_blind']}
>
<Provider store={store}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
<ToastContainer />
</Provider>
</NextThemesProvider>
</NextUIProvider>
</QueryClientProvider>
);
};
provider 에서 NextThemesProvider
컴포넌트와 기본 적용할 baple theme을 만들어 두었었다. 이제 색맹 모드를 의미하는 color_blind
theme을 만들고 theme 간 선택을 가능하게 하는 ui 를 만들 차례이다.
theme 선택을 가능하기 위해 헤더에 위치할 토글 컴포넌트를 하나 만든다.
import { useTheme } from 'next-themes';
import React, { useEffect, useState } from 'react';
import { Switch } from '@nextui-org/react';
const ThemeSwitcher = () => {
const [mounted, setMounted] = useState(false);
const { setTheme, theme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
const themeToggleHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setTheme('baple');
} else {
setTheme('color_blind');
}
};
if (!mounted) return null;
return <Switch defaultSelected onChange={themeToggleHandler} />;
};
export default ThemeSwitcher;
위와 같은 모습으로 비교적 간단한 컴포넌트이다.
Switch 자체는 nextUI 에서 제공하는 것을 가져다 사용했고, theme을 설정하는 것은 next-themes
라는 라이브러리를 사용했다. next-themes에서는 setTheme 을 제공하는데 이를 통해 원하는 theme 으로 변경이 가능하고, theme도 제공하는데 이를 통해서 현재 적용된 theme 을 읽어오고 이를 조건부 렌더링의 조건으로 사용하는 것이 가능하다.
mounted 상태는 에러를 방지하기 위해 필수이다. 지금 사용하고 있는 theme 적용 방식 자체가 TailwindCSS 에서 dark 모드 적용하는 방식을 차용해온 것이라 DOM 이 요구되고 컴포넌트가 마운트되기 전에는 DOM 이 불완전 할 수 있기 때문이라고 한다.
TailwindCSS에서 Theme 을 적용하는 방식에 대해서는 이 공식문서를 참조하기 바란다. 일단은 TailwindCSS 에서 dark 모드를 적용하는 방식과 동일하게 최상단 html에 class로 theme이름을 집어넣고 tailwind는 이를 인식한뒤 그 이름에 맞는 theme과 색상들을 적용한다고 이해하는 것으로 충분하다.
이렇게 가장 윗 줄에 있는 html 태그의 class 로 theme 의 이름이 들어가게 되고 tailwind 가 이를 인식하고 자동으로 적용한다.
theme 을 토글할 수 있는 ui 를 만들었으니 이제 theme을 정의해야 한다. 기존에 baple theme을 정의해본 경험이 있어서 많이 헤메지 않고 수월하게 진행할 수 있었다.
color_blind: {
extend: 'dark',
colors: {
background: 'black',
foreground: 'white',
primary: {
DEFAULT: '#66b6ff',
foreground: 'white',
},
secondary: {
DEFAULT: '#1E1E1E',
foreground: 'black',
},
focus: '#FFB629',
},
layout: {
disabledOpacity: '0.3',
radius: {
small: '4px',
medium: '6px',
large: '8px',
},
borderWidth: {
small: '1px',
medium: '2px',
large: '3px',
},
},
},
다음은 tailwind.config.js 의 일부이다. 여기서 color_blind theme을 정의하고 있다.
고대비를 추구하므로 기본적으로 dark theme을 확장하여 사용하고 있으며 primary color 로 위에서 언급한 파란색(#66b6ff)을 설정해 주었다. 그 외에도 사소한 설정들이 되어있는 모습이다.
새로 만든 theme이 정상적으로 적용되었는데 문제가 하나 발생, 웹 어플리케이션 내부에서 사용하고 있는 수많은 아이콘들은 디자이너님이 만들어준 svg 파일을 next Image 컴포넌트에서 뿌리는 방식이었는데, tailwindCss 가 svg 파일의 색을 바꿔줄 수는 없었기 때문에 theme이 바뀌어도 아이콘들은 이전과 그대로 보라색으로 표시되었다.
이를 해결하기 위해 기존 아이콘들을 복제해서 별도의 폴더에 넣고 svg 파일 내부에서 fill
속성에 설정되어 있는 색을 #66b6ff 으로 바꿔주었다.
그렇게 color_blind theme 용 아이콘을 따로 만든뒤 theme 이 바뀔때 바뀐 theme에 맞는 아이콘으로 갈아끼워지게 삼항연산자를 작성하였다. 아래는 그 예시이다.
<Image
src={`/images/icons/${
theme === 'baple'
? 'comment_select.svg'
: 'CBicons/CBcomment_select.svg'
}`}
width={20}
height={20}
alt='comment icon'
/>