AI 보조 개발11 분 읽기

대비 검사는 전부 통과했습니다 — 화면은 죽어 있었습니다

앱 26개의 색을 자동으로 갈아끼우고 WCAG 대비 감사를 돌렸습니다. 결함 0. 그런데 사용자가 '폰트가 거의 안 보인다'고 했습니다. 감사가 재지 않은 것은 대비가 아니라 밝기의 역할이었습니다.

#accessibility#design-system#first-principles#reality-check#automation-pipeline
왼쪽 패널은 대비·넘침·오류 검사가 전부 통과라고 보고하는 감사 결과, 오른쪽 패널은 같은 화면에서 크림색 카드가 카키색이 되고 검은 막이 갈색 필름이 된 실제 모습
왼쪽은 제가 잰 것, 오른쪽은 사용자가 본 것입니다.

웹앱 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.13

26개 앱 중 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개

지금 당신의 파이프라인에 그대로 대볼 수 있는 것만 적습니다.

  1. 당신의 색 파서는 oklch()·color-mix()를 읽습니까? 못 읽으면 조용히 흰색으로 떨어지고, 그 순간 감사는 거짓말을 시작합니다. 파싱하지 말고 렌더러에 물어보세요.
  2. 다크 모드를 실제로 열어본 검사가 있습니까? 헤드리스 도구 기본값은 대개 라이트입니다. 스킴을 명시하지 않았다면 절반은 검사된 적이 없습니다.
  3. 배경을 글자 기준으로 조정하는 규칙이 있습니까? 카드 배경을 텍스트 대비 목표로 어둡게 만들면, 그 위 글자가 1.49:1이 됩니다. 실제로 그렇게 만들었습니다.

솔직한 부분

이 사고는 제가 냈고, 사용자가 화면을 보고 알려줘서 알았습니다. 통과한 감사 결과를 저는 그대로 믿고 "전 페이지 결함 0"이라고 보고할 참이었습니다. 전에도 초록불만 보고 결론을 낸 적이 있습니다.

지금은 대비·넘침·오류에 예외를 두지 않고, 팔레트 예외만 근거와 함께 감사기에 코드로 적습니다. 색이 곧 콘텐츠인 퍼스널컬러 스와치, 자매앱을 각자 고유색으로 보여주는 마크, 인식이 목적인 타사 브랜드색 — 이 셋뿐입니다. 산문으로 "이건 괜찮다"고 적어두면 리포트는 영원히 참이 되지 않습니다.

마지막 감사는 43개 앱 중 38개가 결함 0이었습니다. 남은 것도 대부분 배포 전파가 안 끝난 옛 빌드였습니다. 그래도 이 글의 결론은 숫자가 아닙니다.

측정이 통과했다는 것은 "괜찮다"가 아니라 "내가 잰 것만 괜찮다"는 뜻입니다.

혹시 지금 자동 검사를 돌리고 계신다면, 통과 리포트를 한 번 의심해 보세요. 검사가 열어본 적 없는 경로가 하나쯤은 있을 겁니다. 저는 그게 다크 모드였습니다. 당신은 무엇이었는지 궁금합니다.

관련 글