웹앱 26개의 디자인 시스템을 하루에 통일했습니다. 색 토큰을 자동으로 갈아끼우고, 브라우저를 띄워 전 라우트의 WCAG 대비를 쟀습니다. 결함 0.
그리고 사용자에게서 한 줄이 왔습니다.
"욕쟁이 할매, 폰트가 거의 안 보이는데, 배포 전이라 그래?"
배포 전이 아니었습니다. 이미 나가 있었고, 제 감사는 그 화면을 통과시켰습니다.
당신의 자동 검사는 "읽히는가"를 재고 있습니까, "보기 좋은가"를 재고 있습니까? 저는 둘이 같은 줄 알았습니다.
감사는 왜 통과했나
제 규칙은 이랬습니다. 색의 채도가 0.12를 넘으면 강조색으로 보고, 본문 글자 밝기(상대휘도 0.13)로 눌러 대비를 확보한다.
이 규칙은 글자에 대해서는 옳습니다. 문제는 화면의 모든 색이 글자가 아니라는 것입니다.
- 크림색 카드
rgba(255,244,218)— 상대휘도 0.91. 채도가 있으니 강조색으로 분류됐고, 0.13으로 눌렸습니다. 카드가 카키색이 됐습니다. - 가독성용 검은 막
rgba(13,8,6,0.78)— 상대휘도 0.003. 역시 채도가 있어서 밝혀졌습니다. 화면 전체에 갈색 필름이 씌워졌습니다.
그 위에 얹힌 글자 색은 그대로였습니다. 그래서 글자와 배경의 대비는 여전히 기준을 넘었습니다. 카키색 카드 위의 검은 글자는 4.5:1을 통과합니다. 통과하는데 아무도 읽고 싶지 않은 화면이 됐습니다.
대비는 두 색의 관계입니다. 제가 부순 것은 관계가 아니라 역할이었습니다. 어느 것이 지면이고 어느 것이 잉크인지, 어느 것이 앞이고 어느 것이 뒤인지. 그건 대비 공식에 안 들어갑니다.
판정기를 두 번 잘못 만들었습니다
"그럼 역할 뒤집힘을 재자"고 정하고 스크립트를 짰습니다. 첫 판정기는 diff의 -/+ 줄을 순서대로 짝지어 밝기 변화를 봤습니다. 결과: 최악의 앱이 0건으로 통과했습니다. 줄 단위 짝짓기가 어긋나 있었습니다.
두 번째는 파일 전체 색의 밝기 분포에서 양극단(밝은 면·깊은 그림자)이 사라졌는지 봤습니다. 같은 앱이 또 통과했습니다. 토큰이 추가되면서 분포가 흔들려 신호가 묻혔습니다.
세 번째에야 잡혔습니다. 제 재도색은 색을 제자리에서 1:1로 치환했으니, 생성한 블록만 떼면 기준본과 현재본의 색 나열이 그대로 대응합니다. 짝을 지어 밝기만 비교했습니다.
어두운 막이 밝아짐: L 0.003 -> 0.107
밝은 면이 어두워짐: L 0.91 -> 0.1326개 앱 중 24개에서 나왔습니다. 눈으로 본 건 한 개였습니다.
감사기 자체가 세 번 거짓말했습니다
역할 판정을 고치는 동안, 원래 감사기에서도 버그가 셋 나왔습니다. 전부 "통과"를 만들어내는 종류였습니다.
1. oklch()를 못 읽었습니다. Tailwind v4는 색을 oklch()로 내보냅니다. 제 파서는 rgb()만 읽었고, 못 읽은 배경은 흰색으로 처리했습니다. 그래서 "흰 글자가 흰 배경에" 90건이라는 유령이 떴습니다. 19개 앱을 "고치러" 갈 뻔했습니다. 해법은 파서를 늘리는 게 아니라 파싱을 그만두는 것이었습니다 — 캔버스 1픽셀에 그 색을 칠하고 픽셀을 읽으면 브라우저가 대신 풀어줍니다.
2. waitUntil: 'networkidle'이 광고 스크립트 때문에 영영 안 왔습니다. 멀쩡한 페이지 6개가 THROW로 찍혔습니다.
3. Playwright 기본 색상 스킴이 라이트입니다. 다크 모드는 아예 검사된 적이 없었습니다. 따로 돌리자 허브에서 결함이 쏟아졌습니다.
세 번째가 제일 무섭습니다. 저는 "라이트·다크 전부 통과"라고 보고할 참이었습니다. 다크는 한 번도 열어본 적이 없었는데요.
여기서 갈립니다 — 당신이라면?
24개 앱이 망가졌습니다. 두 갈래가 있습니다.
A. 규칙을 정교하게 만든다. 밝은 면은 밝게, 어두운 막은 어둡게, 중간만 조정하도록 조건을 더한다. B. 자동 도구가 디자인 색을 건드리는 것 자체를 그만둔다.
저는 A를 먼저 했습니다. 밝기 역할을 보존하는 3판 규칙을 짰고, 돌려보니 크림색(#fff4da)이 회색(#eeedeb)으로 바랬습니다. 채도 상한이 이번엔 따뜻한 크림의 정체성을 죽였습니다.
그래서 B로 갔습니다. 인주 팔레트는 토큰(지면·먹·강조색)까지만 관여하고, 앱 고유 색·그라디언트·스크림은 자동 도구가 건드리지 않습니다. 파괴적인 패스 두 개를 26개 앱에서 되돌렸습니다.
되돌리자 통일성은 낮아졌습니다. 보라 달, 핑크 마크가 돌아왔습니다. 그건 나중에 손으로 오행 색에 배정했습니다 — 색상각만 돌리고 밝기는 그대로. 밝기를 건드리는 순간 다시 같은 사고가 납니다.
덤으로 알게 된 것
재도색 커밋을 git add -A로 만들었습니다. 나중에 보니 그 커밋이 커밋되지 않은 채 워킹트리에 있던 다른 작업(부엌 사진 배경을 쓰는 새 디자인)을 함께 삼켰습니다. 남의 작업을 제 커밋에 흡수한 뒤 그 색을 망가뜨린 셈입니다.
일괄 작업 전에 git status부터 보는 이유가 이것입니다.
자가진단 3개
지금 당신의 파이프라인에 그대로 대볼 수 있는 것만 적습니다.
- 당신의 색 파서는
oklch()·color-mix()를 읽습니까? 못 읽으면 조용히 흰색으로 떨어지고, 그 순간 감사는 거짓말을 시작합니다. 파싱하지 말고 렌더러에 물어보세요. - 다크 모드를 실제로 열어본 검사가 있습니까? 헤드리스 도구 기본값은 대개 라이트입니다. 스킴을 명시하지 않았다면 절반은 검사된 적이 없습니다.
- 배경을 글자 기준으로 조정하는 규칙이 있습니까? 카드 배경을 텍스트 대비 목표로 어둡게 만들면, 그 위 글자가 1.49:1이 됩니다. 실제로 그렇게 만들었습니다.
솔직한 부분
이 사고는 제가 냈고, 사용자가 화면을 보고 알려줘서 알았습니다. 통과한 감사 결과를 저는 그대로 믿고 "전 페이지 결함 0"이라고 보고할 참이었습니다. 전에도 초록불만 보고 결론을 낸 적이 있습니다.
지금은 대비·넘침·오류에 예외를 두지 않고, 팔레트 예외만 근거와 함께 감사기에 코드로 적습니다. 색이 곧 콘텐츠인 퍼스널컬러 스와치, 자매앱을 각자 고유색으로 보여주는 마크, 인식이 목적인 타사 브랜드색 — 이 셋뿐입니다. 산문으로 "이건 괜찮다"고 적어두면 리포트는 영원히 참이 되지 않습니다.
마지막 감사는 43개 앱 중 38개가 결함 0이었습니다. 남은 것도 대부분 배포 전파가 안 끝난 옛 빌드였습니다. 그래도 이 글의 결론은 숫자가 아닙니다.
측정이 통과했다는 것은 "괜찮다"가 아니라 "내가 잰 것만 괜찮다"는 뜻입니다.
혹시 지금 자동 검사를 돌리고 계신다면, 통과 리포트를 한 번 의심해 보세요. 검사가 열어본 적 없는 경로가 하나쯤은 있을 겁니다. 저는 그게 다크 모드였습니다. 당신은 무엇이었는지 궁금합니다.