HEX 코드 두 개로 명도 대비를 손으로 계산하는 법

색이 괜찮아 보인다고 생각했는데 접근성 검사를 돌렸더니 미달이 뜬 적 있다면, 아마 그 숫자가 낯설게 느껴졌을 거다. 4.5:1이라는 기준은 어디서 오는 건지, 내 색 조합이 얼마나 아슬아슬한 건지 감이 잘 안 잡힌다. 그게 당연한 게, 우리는 대부분 체커에 HEX 코드를 넣고 색을 피해가는 방식으로만 접근하니까. 한 번쯤 공식을 직접 따라가 보면 그 숫자가 달리 보이기 시작한다.
WCAG 2.1이 요구하는 명도 대비 기준
WCAG 2.1 성공 기준 1.4.3은 텍스트와 배경 사이의 명도 대비 비율을 준수 수준별로 나눠 놓는다.
AA 수준(최소 준수)에서 일반 텍스트는 4.5:1 이상, 큰 텍스트는 3:1 이상. AAA 수준(강화 기준)에서는 일반 텍스트 7:1 이상, 큰 텍스트 4.5:1 이상이다.
'큰 텍스트'는 18pt(약 24px) 이상이거나, Bold 처리된 14pt(약 18.67px) 이상을 말한다. 비활성화 상태의 UI 요소, 순수 장식, 로고타입에는 이 기준이 적용되지 않는다.
대비 비율의 범위는 1:1(완전히 같은 색)부터 21:1(흰 배경 위 검은 텍스트)까지다. 이 숫자가 구체적으로 어떻게 나오는지, 아래 4단계를 따라가 보자.
4단계 계산 절차
예시 색상으로 #777777(회색 텍스트)을 흰 배경 #FFFFFF 위에 올리는 경우를 계산한다.
1단계. HEX를 정규화된 sRGB 값으로 변환
HEX 코드를 R·G·B 채널로 분리해 각각 10진수로 바꾼 뒤 255로 나눈다. 결과는 0~1 사이의 소수다.
#777777의 경우:
0x77= 119 → 119 ÷ 255 = 0.4667
세 채널 모두 77이므로 R·G·B 전부 0.4667이다.
2단계. 감마 보정 선형화
모니터의 감마 특성을 제거해야 물리적 빛의 양에 대응하는 선형 값이 나온다. 각 채널에 아래 조건을 적용한다.
- 채널 값 ≤ 0.04045 → 값 ÷ 12.92
- 채널 값 > 0.04045 → ((값 + 0.055) ÷ 1.055)^2.4
0.4667은 0.04045를 초과하므로 두 번째 공식을 쓴다. 괄호 안을 먼저 풀면:
- 0.4667 + 0.055 = 0.5217
- 0.5217 ÷ 1.055 = 0.4947
- 0.4947^2.4 ≈ 0.1845
처음에는 ^2.4 지수 계산이 낯설게 느껴지는데, 전자계산기나 파이썬 ** 2.4로 바로 구할 수 있으니 공식 구조만 기억해 두면 된다.
3단계. 상대 휘도(L) 계산
선형화된 R·G·B 값에 가중치를 곱해 합산한다. 가중치는 인간 눈의 파장별 감도를 반영한다. 초록이 가장 밝게 느껴지고(0.7152), 파랑이 가장 어둡게 느껴진다(0.0722).
L = 0.2126 × R_선형 + 0.7152 × G_선형 + 0.0722 × B_선형
#777777은 R·G·B가 모두 같으므로:
L = (0.2126 + 0.7152 + 0.0722) × 0.1845 = 1.0 × 0.1845 = 0.1845
참고로 흰색(#FFFFFF)의 L은 1.0, 검은색(#000000)의 L은 0.0이다.
4단계. 대비 비율 산출
두 색의 상대 휘도 중 밝은 쪽을 L1, 어두운 쪽을 L2로 놓는다.
대비 비율 = (L1 + 0.05) ÷ (L2 + 0.05)
흰 배경 #FFFFFF(L=1.0) 위에 #777777(L=0.1845):
(1.0 + 0.05) ÷ (0.1845 + 0.05) = 1.05 ÷ 0.2345 ≈ 4.48:1
AA 기준인 4.5:1에 아슬아슬하게 미달한다.
AA 기준선을 가르는 한 단계 차이
#777777과 #767676은 16진수 한 단계 차이다. 이 차이가 실제로 어떤 결과를 만드는지 비교해 보자.
#777777 on #FFFFFF
0x77= 119 → sRGB 0.4667 → 선형화 0.1845- L = 0.1845
- 대비 비율: 1.05 ÷ 0.2345 ≈ 4.48:1 → AA 기준(4.5:1) 미달
#767676 on #FFFFFF
0x76= 118 → sRGB 0.4627 → 선형화 0.1812- L = 0.1812
- 대비 비율: 1.05 ÷ 0.2312 ≈ 4.54:1 → AA 통과
채널 값이 119에서 118로 딱 하나 줄었을 뿐인데 결과가 바뀐다. 색 대비 체커가 이 두 색을 다르게 판정하는 이유가 정확히 이 공식에 있다. 감으로 '비슷한 회색'이라고 넘겼다가는 검수를 통과하지 못한다.
AA 미달이 나왔을 때 적용하는 기준
계산으로 나온 대비 비율, 아래 기준에 맞춰 보면 된다.
텍스트 크기와 스타일부터 확인한다.
- 24px(18pt) 미만, 일반 굵기 → 일반 텍스트 기준: AA 4.5:1 · AAA 7:1
- 24px(18pt) 이상 → 큰 텍스트 기준: AA 3:1 · AAA 4.5:1
- 18.67px(14pt) 이상 + Bold → 큰 텍스트 기준: AA 3:1 · AAA 4.5:1
본문 텍스트는 대부분 일반 텍스트 기준(4.5:1)에 걸린다. H1이나 섹션 제목처럼 크게 쓰는 경우에만 3:1로 완화된다. 미묘한 경계에 있는 색상은 이 절차를 직접 돌려보고 숫자로 판단하는 편이 확실하다.
계산을 익히고 나면 달라지는 것
4단계를 한 번 직접 따라가 보고 나면, 이후에는 체커의 숫자가 단순한 합격·불합격이 아니라 얼마나 여유 있는지, 어느 채널 값을 얼마나 내려야 통과할지가 보이기 시작한다. 감으로 넘기던 판단을 숫자로 붙잡을 수 있게 된다.
색 대비를 맞췄어도 폰트 자체가 너무 가늘거나 크기가 작으면 가독성 문제는 따로 남는다. 웹에 올릴 폰트를 고르는 중이라면, 폰트비(fontbee.waffle-gl.org)에서 폰트별 사용 범위(웹사이트·인쇄·영상 등)와 라이선스 안내를 확인하고 @font-face 웹폰트 코드를 바로 복사할 수 있다.
이 글이 도움이 됐나요?
여러분의 반응이 다음 글의 방향이 돼요


