Colbrush: 접근성 문제를 라이브러리로 풀어낸 과정
1. 왜 웹앱이 아니라 라이브러리였나?
Colbrush는 오픈소스 개발자대회 사회문제 전형에 참가하면서 시작한 프로젝트입니다.
초기에는 여러 아이디어가 있었지만, 구현 가능성과 완성도를 함께 고려했을 때 가장 설득력 있었던 문제는 색상 접근성이었습니다.
색각 이상 사용자는 색상만으로 상태를 구분하는 UI에서 중요한 차이를 놓칠 수 있습니다.
문제는 이 이슈가 특정 서비스 하나에만 국한되지 않는다는 점이었습니다. 서비스마다 같은 문제를 다시 해결해야 하고, 그 비용 때문에 접근성이 뒤로 밀리는 경우도 많습니다.
그래서 저는 "특정 앱 하나를 잘 만드는 것"보다, 개발자가 자신의 프로젝트에 바로 가져다 쓸 수 있는 재사용 가능한 도구를 만드는 쪽이 더 의미 있다고 판단했습니다. Colbrush는 그 판단에서 나온 npm 배포형 React 라이브러리입니다.
2. 목표: 원래 디자인을 해치지 않으면서도 구분 가능하게
팀 안에서 세운 기준은 단순했습니다.
- 색각 이상 사용자도 구분이 필요한 색을 충분히 구별할 수 있어야 한다.
- 그렇다고 원래 디자인과 완전히 동떨어진 팔레트가 되어서는 안 된다.
- 처음부터 비슷한 계열로 정의된 색은 과하게 벌리지 않고, 실제로 구분이 필요한 상태 색상 위주로 차이를 확보해야 한다.
이 기준을 맞추기 위해 팀 내부에서는 Delta E 12 이상을 목표로 삼았습니다.
즉, 단순히 색을 바꾸는 것이 아니라 "얼마나 다르게 느껴지는가"를 기준으로 접근성을 다루려 했습니다.
3. 내가 맡은 역할
제가 맡은 범위는 라이브러리의 핵심 동작과 외부 사용 흐름에 가까웠습니다.
- 글로벌 스타일 파일 파싱
- 기준 색상으로부터
100 ~ 900variation 생성 - CLI 명령어 생성 로직과 메인 실행 흐름 구현
ThemeProvider,ThemeSwitcher구현- 데모 페이지의 사용법 섹션 구현
특히 중요했던 것은 "라이브러리를 실제 프로젝트에서 어떻게 쓰게 만들 것인가"였습니다.
웹앱은 내 환경에서만 잘 돌아가도 어느 정도 성과가 나오지만, 라이브러리는 다른 사람이 자기 프로젝트에 붙였을 때도 안정적으로 동작해야 합니다.
4. 색상 토큰을 읽고, 외부 프로젝트가 바로 쓸 수 있게 만들기
Colbrush는 기존 CSS 변수를 파싱해 색상 토큰을 읽고, 이를 기반으로 색각 유형별 테마를 생성하는 구조입니다.
저는 이 과정에서 색상 데이터를 변환하는 로직과 실제 앱에서 테마 상태를 유지하는 런타임 레이어를 함께 다뤘습니다.
ThemeProvider는 테마, 언어, 시뮬레이션 상태를 묶어 관리하고, 이를 localStorage와 document.documentElement에 반영해 앱 전역에서 일관된 상태를 유지하도록 설계했습니다.
ThemeSwitcher는 이 상태를 실제 사용자 인터페이스로 노출하는 역할을 했습니다.
즉, Colbrush는 "색상을 계산하는 도구"에 그치지 않고, 계산된 결과를 실제 프로젝트에서 적용하고 체험할 수 있는 구조까지 포함한 라이브러리였습니다.
5. 트러블슈팅 1: SVG 아이콘을 런타임 변환에 맡기지 않기
가장 먼저 부딪힌 문제는 ThemeSwitcher 내부 SVG 아이콘이 빌드 환경에 따라 깨지거나 사라지는 현상이었습니다.
viewBox가 삭제되면서 비율이 깨짐?reactimport 방식이 환경에 따라 일관되지 않음
처음에는 단순한 Vite 설정 문제처럼 보였지만, 실제로는 SVGO 기본 최적화와 런타임 변환 방식이 함께 얽혀 있었습니다.
결국 런타임 변환에 의존하지 않고, 아예 빌드 전에 SVG를 .tsx 컴포넌트로 변환하는 쪽으로 방향을 바꿨습니다.
cjs// svgr.config.cjs module.exports = { typescript: true, svgo: true, svgoConfig: { plugins: [ { name: 'preset-default', params: { overrides: { removeViewBox: false } }, }, { name: 'removeDimensions', active: true }, ], }, };
이렇게 전환한 뒤에는 아이콘 import가 안정화됐고, 팀 내에서도 아이콘 처리 규칙을 명확히 공유할 수 있었습니다.
6. 트러블슈팅 2: Tailwind를 두 번 불러오면 사용자 프로젝트가 깨진다
라이브러리 배포 후 받은 가장 치명적인 피드백은 이것이었습니다.
"Colbrush를 설치하니까 기존 프로젝트의 반응형 스타일이 깨집니다."
원인은 라이브러리 내부 스타일 파일에서 @import 'tailwindcss'를 다시 호출하고 있었기 때문이었습니다.
사용자 프로젝트도 이미 Tailwind를 가지고 있는데, 라이브러리에서 다시 Base Layer를 주입하면서 레이어 순서가 꼬여버린 것입니다.
이 문제를 해결하면서 배운 점은 분명했습니다.
라이브러리는 "내 프로젝트에서 편한 구조"가 아니라, "사용자 환경과 최대한 충돌하지 않는 구조"가 우선이어야 합니다.
결국 라이브러리 내부에서 Tailwind 의존성을 제거하고, styles.css는 우리가 직접 생성한 CSS 변수와 최소한의 레이아웃 스타일만 남기는 구조로 정리했습니다.
그 결과 Colbrush는 특정 프레임워크에 덜 묶인, 훨씬 안전한 형태가 됐습니다.
7. 트러블슈팅 3: 로컬에서는 되는데 번들 후엔 깨지는 CJS/MJS 문제
이 프로젝트에서 가장 많이 배운 지점은 사실 알고리즘이 아니라 번들링이었습니다.
2차 개발 과정에서 rgb, rgba, oklch 등 다양한 색상 표기법을 지원하도록 확장하던 중,
colorjs.io 기반 변환 로직이 번들 결과물에서는 깨지는 문제를 만났습니다.
문제의 핵심은 tsup이 CommonJS 번들을 만들면서 모듈을 { default: ColorCtor } 형태로 감쌌고,
런타임에서 생성자로 직접 쓰던 방식과 충돌했다는 점이었습니다.
이 경험은 라이브러리 개발의 기준을 완전히 바꿨습니다.
- 로컬에서 돌아간다고 끝이 아니다.
- 빌드 결과물이 어떤 포맷으로 소비되는지 봐야 한다.
- 외부 라이브러리의 import 방식도 번들러를 통과하면 달라질 수 있다.
웹앱 개발에서는 상대적으로 덜 체감했던 문제였지만, 배포형 라이브러리에서는 이 부분이 오히려 핵심이었습니다.
8. 마무리
Colbrush를 만들면서 접근성은 "추가 기능"이 아니라 개발 단계에서부터 고려해야 하는 기본 품질이라는 관점을 더 분명하게 갖게 됐습니다.
동시에 라이브러리 개발은 기능 구현만으로 끝나지 않고, 문서화, 배포 포맷, 스타일 충돌, 외부 소비 환경까지 함께 설계해야 비로소 사용 가능한 형태가 된다는 점도 배웠습니다.
오픈소스 개발자대회 수상과 다운로드 수 자체도 의미 있었지만, 더 큰 수확은 "다른 개발자가 실제로 사용할 수 있는 구조"를 고민하게 되었다는 점이었습니다.
Enjoyed this article? Check out more projects and posts on my portfolio.
Explore this project