~ / posts / developer

WCAG 명도 대비 4.5:1 기준과 계산 공식, 통과하는 색 찾는 법

본문 글자는 배경과 명도 대비 4.5:1, 큰 글자와 UI 요소는 3:1이 WCAG AA 기준이에요. 상대 휘도 공식과 계산 코드, 흰 배경 회색의 하한선 #767676, 인기 파랑 #3B82F6 버튼이 흰 글자와 3.67:1로 미달인 이유까지 직접 계산해 정리했어요.

디자인 검수에서 "이 회색 글씨 접근성 기준 통과해요?"라는 질문을 받으면 예전엔 감으로 답했어요. 결론부터 말하면 본문 글자는 배경과의 명도 대비가 4.5:1 이상, 큰 글자(약 24px 이상 또는 굵게 약 18.7px 이상)와 아이콘·입력창 테두리 같은 UI 요소는 3:1 이상이면 WCAG 2.x AA 기준을 통과해요. 흰 배경 회색 글씨라면 #767676 이 4.5:1을 넘는 가장 연한 회색이에요.

이 두 숫자만 기억해도 접근성 점검에서 지적받는 색 문제의 대부분은 피할 수 있어요. 그런데 이 숫자가 정확히 뭘 재는 건지 알고 나면 색을 고를 때 훨씬 덜 헤매요. 공식, 계산 코드, 실제 색들의 대비값을 직접 계산해서 정리했어요.

핵심 요약

WCAG 2.x AA 기준은 일반 텍스트 4.5:1, 큰 텍스트 3:1, UI 구성요소·그래픽 3:1이에요. AAA는 7:1과 4.5:1이에요.

대비율은 두 색의 상대 휘도로 (밝은 쪽 + 0.05) ÷ (어두운 쪽 + 0.05)로 계산하고, 최대는 흰색과 검정의 21:1이에요.

흰 배경에서 #999는 2.84:1로 미달이고, #767676이 4.54:1로 통과하는 가장 연한 회색이에요.

같은 HSL 밝기라도 노랑·초록 계열은 대비가 낮고 파랑 계열은 높아서, 기준을 맞추려면 색상마다 내려야 할 밝기가 달라요.

WCAG 명도 대비 기준표 (AA, AAA)

WCAG 2.x(웹 콘텐츠 접근성 지침) 기준이에요.

대상AAAAA
일반 텍스트4.5:17:1
큰 텍스트 (18pt 이상, 또는 14pt 이상 굵게)3:14.5:1
아이콘·입력창 테두리 같은 UI 구성요소, 그래픽3:1해당 없음
비활성(disabled) 요소, 로고, 장식기준 없음기준 없음

18pt는 CSS로 24px, 14pt는 약 18.66px이에요. 보통 법적·조달 요구사항이나 사내 기준에서 목표로 삼는 건 AA 단계예요. 국내 웹 접근성 인증에서 쓰는 한국형 웹 콘텐츠 접근성 지침(KWCAG)도 텍스트 명도 대비 4.5:1을 기준으로 다뤄요(세부 항목은 개정될 수 있으니 최신 지침을 확인하세요).

UI 구성요소 줄은 놓치기 쉬워요. 연한 회색 테두리만 있는 입력창은 글자 대비는 통과해도 "어디가 입력칸인지" 안 보일 수 있어요. 체크박스 테두리, 토글 스위치, 아이콘 버튼, 차트의 선도 3:1 대상이에요.

대비율 계산 공식 — 상대 휘도부터

대비율은 두 색의 상대 휘도(relative luminance) 로 구해요. 상대 휘도는 색이 사람 눈에 얼마나 밝게 느껴지는지를 0(검정)~1(흰색)로 나타낸 값이에요.

1) 각 채널을 0~1로:        c = 채널값 / 255
2) 감마 해제(선형화):      c ≤ 0.04045 이면 c / 12.92
                          아니면 ((c + 0.055) / 1.055) ^ 2.4
3) 상대 휘도:              L = 0.2126 × R + 0.7152 × G + 0.0722 × B
4) 대비율:                (밝은 쪽 L + 0.05) / (어두운 쪽 L + 0.05)

손으로 한 번 해 볼게요. #767676 은 세 채널이 모두 0x76 = 118이에요.

0.05를 더하는 건 화면 반사광(flare)을 반영한 보정이에요. 그래서 흰색과 검정의 대비가 무한대가 아니라 (1.05 / 0.05) = 21:1로 최대가 돼요. 같은 색끼리는 1:1이고요. 오래된 WCAG 문서에는 0.04045 대신 0.03928이 적혀 있는데, 8비트 색에서는 결과가 같아요.

초록 가중치가 0.7을 넘는다는 게 핵심이에요. 이 때문에 같은 채도라도 노랑·초록 계열은 흰 배경에서 대비가 형편없이 나오고, 파랑 계열은 생각보다 잘 나와요. HSL의 L 값이 같아도 밝기가 다른 이유는 HEX·RGB·HSL 차이 글에서 그림으로 비교했어요.

대비율 계산 코드 (JavaScript, Python)

function luminance(hex) {
  const [r, g, b] = hex.replace('#', '').match(/../g).map(h => {
    const c = parseInt(h, 16) / 255;
    return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function contrast(a, b) {
  const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (hi + 0.05) / (lo + 0.05);
}

contrast('#767676', '#ffffff').toFixed(2); // '4.54'
contrast('#3B82F6', '#ffffff').toFixed(2); // '3.67'
def luminance(hex_):
    def lin(c):
        c /= 255
        return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = (lin(int(hex_.lstrip('#')[i:i+2], 16)) for i in (0, 2, 4))
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def contrast(a, b):
    hi, lo = sorted([luminance(a), luminance(b)], reverse=True)
    return (hi + 0.05) / (lo + 0.05)

판정할 때 반올림하지 마세요. 4.47:1을 4.5로 반올림해서 통과시키면 안 돼요. WCAG는 계산값이 기준 이상이어야 통과로 봐요. 위 코드는 3자리 HEX(#999)를 받지 않으니, 필요하면 먼저 6자리로 펼치세요.

흰 배경 회색 텍스트, 어디까지 연해도 될까

보조 텍스트, 캡션, placeholder에 쓰는 회색이 가장 많이 걸려요. 흰 배경 위 회색들을 계산해 봤어요.

흰 배경(#FFFFFF) 위 회색 글자의 명도 대비
#555555#555555 · 7.5:17.5:1#666666#666666 · 5.7:15.7:1#767676#767676 · 4.5:14.5:1#777777#777777 · 4.5:14.5:1#888888#888888 · 3.5:13.5:1#959595#959595 · 3:13:1#999999#999999 · 2.8:12.8:1#AAAAAA#AAAAAA · 2.3:12.3:1#CCCCCC#CCCCCC · 1.6:11.6:1

4.5 이상이 본문 AA, 3 이상이 큰 글자 AA — #767676과 #777777 사이가 경계

단위: :1 · 자료: WCAG 2.x 상대 휘도 공식으로 직접 계산 (소수 셋째 자리 버림)
표로 보기
구분대비율
#5555557.5
#6666665.7
#7676764.5
#7777774.5
#8888883.5
#9595953
#9999992.8
#AAAAAA2.3
#CCCCCC1.6

#767676 은 4.54로 통과, 한 단계 연한 #777777 은 4.47로 미달이에요. 그래서 "흰 배경 회색 텍스트의 하한선은 #767676"이라는 말이 널리 인용돼요. 자주 쓰는 #999 는 2.84로 큰 글자 기준(3:1)에도 못 미쳐요. 배경이 순백이 아니라 #F5F5F5 같은 연회색이면 기준을 넘기 위해 글자를 더 진하게 해야 하니, 실제 배경색으로 계산하세요.

브랜드 컬러 버튼 위 흰 글자가 실패하는 이유

버튼은 더 자주 걸려요. 흔히 쓰는 브랜드 색들에 흰 글자와 검정 글자를 올렸을 때의 대비를 계산했어요.

흰 배경 위 회색 글자 네 가지와 브랜드 색 버튼 다섯 가지의 실제 모습, 각각의 명도 대비값과 통과 여부
숫자로 보면 애매하던 차이가 눈으로는 분명하다
브랜드 색 배경 위 흰 글자 vs 검정 글자 대비
흰 글자검정 글자
024681012파랑 3B82F6 · 흰 글자 3.7:1파랑 3B82F6 · 검정 글자 5.7:1파랑 3B82F6파랑 2563EB · 흰 글자 5.2:1파랑 2563EB · 검정 글자 4.1:1파랑 2563EB주황 F97316 · 흰 글자 2.8:1주황 F97316 · 검정 글자 7.5:1주황 F97316초록 22C55E · 흰 글자 2.3:1초록 22C55E · 검정 글자 9.2:1초록 22C55E노랑 EAB308 · 흰 글자 1.9:1노랑 EAB308 · 검정 글자 10.9:1노랑 EAB308빨강 EF4444 · 흰 글자 3.8:1빨강 EF4444 · 검정 글자 5.6:1빨강 EF4444보라 8B5CF6 · 흰 글자 4.2:1보라 8B5CF6 · 검정 글자 5:1보라 8B5CF6

중간 밝기의 파랑·빨강·보라는 흰 글자도 검정 글자도 애매하다

단위: :1 · 자료: WCAG 2.x 상대 휘도 공식으로 직접 계산 (#FFFFFF, #000000 기준)
표로 보기
구분흰 글자검정 글자
파랑 3B82F63.75.7
파랑 2563EB5.24.1
주황 F973162.87.5
초록 22C55E2.39.2
노랑 EAB3081.910.9
빨강 EF44443.85.6
보라 8B5CF64.25

놀라운 건 많이 쓰이는 파랑 #3B82F6 이 흰 글자와 3.67:1로 본문 기준 미달이라는 점이에요. 버튼 글자가 24px 이상이거나 굵은 18.7px 이상이면 큰 텍스트로 3:1이 적용돼 통과하지만, 보통 버튼 글자는 14~16px이라 미달이에요. 한 단계 진한 #2563EB 는 5.16으로 통과해요. 주황·초록·노랑처럼 밝은 색은 흰 글자로는 거의 불가능하고, 검정 글자가 훨씬 잘 맞아요.

색상은 유지하고 밝기만 낮춰 기준 맞추기

브랜드 느낌을 지키면서 대비를 맞추려면 색상(H)과 채도(S)는 그대로 두고 밝기(L)만 내려 보면 돼요. 각 색이 흰 글자와 4.5:1을 넘으려면 HSL 밝기를 어디까지 낮춰야 하는지 계산해 봤어요.

원래 색원래 L4.5:1 넘는 최대 L결과 색대비
파랑 #3B82F660%53.8%#1E6FF54.52
주황 #F9731653%39.7%#C554054.52
초록 #22C55E45%31.3%#1788414.53
노랑 #EAB30847%30.0%#9471054.54
빨강 #EF444460%50.5%#EB16164.51
보라 #8B5CF666%64.9%#8655F64.52

보라는 1%p만 낮추면 되는데 노랑은 17%p나 낮춰야 하고, 그러면 사실상 겨자색이 돼요. 노랑·주황 브랜드라면 버튼 글자를 검정 계열로 바꾸는 쪽이 브랜드 색을 지키기 좋아요. 이런 계산을 매번 하기 어려우니, 디자인 시스템에서 단계별 색을 미리 만들고 각 단계의 대비를 표로 정리해 두면 편해요. 그 방법은 디자인 시스템 컬러 스케일 만들기에 정리했어요.

실무에서 자주 걸리는 경우 체크리스트

  1. 회색 보조 텍스트 — 흰 배경에 #999 는 2.84:1로 미달. #767676 이상으로.
  2. 브랜드 컬러 버튼 위 흰 글씨 — 밝은 주황·연두·노랑은 거의 실패. 글씨를 어둡게 하거나 버튼을 진하게.
  3. placeholder — 입력 안내 문구도 텍스트예요. 너무 연하게 해서 걸려요.
  4. 다크 모드 — 라이트 모드 색을 반전만 하면 대비가 무너지는 조합이 생겨요. #767676 은 #121212 배경과는 4.12:1로 미달이에요. 다크 모드 색은 따로 검사하세요.
  5. 이미지 위 텍스트 — 배경이 부분마다 달라서 가장 밝은 부분 기준으로 확인하거나, 반투명 어두운 오버레이를 깔아야 해요.
  6. hover·focus 상태 — 기본 상태만 검사하고 hover 색이나 포커스 링을 빼먹는 경우가 많아요. 포커스 표시도 주변 색과 3:1 이상이 권장돼요.

그리고 숫자가 전부는 아니에요. 대비가 통과해도 글자가 너무 얇거나 작으면 읽기 어렵고, 색만으로 정보를 전달하면(빨간 글씨만으로 에러 표시 등) 색각 이상 사용자는 놓쳐요. 아이콘이나 문구를 함께 쓰는 습관까지 가져가면 좋아요. 참고로 차기 지침(WCAG 3) 초안에서는 글자 크기와 굵기까지 반영하는 APCA라는 새로운 대비 계산법이 논의되고 있지만, 아직 공식 기준은 WCAG 2.x의 대비율이에요.

자동으로 점검하는 방법

색이 수십 개인 서비스를 손으로 다 계산할 수는 없으니 도구를 같이 써요.

자동 검사의 한계도 알아 두세요. 배경 이미지나 그라데이션 위 글자, 반투명 레이어가 겹친 경우, 가상 요소로 그린 배경은 도구가 실제 배경색을 정확히 모를 수 있어요. 이런 곳은 스크린숏에서 색을 직접 찍어 계산하는 게 확실해요.

자주 묻는 질문

명도 대비 4.5:1은 어떻게 계산하나요?

두 색 각각의 상대 휘도를 구한 뒤 (밝은 쪽 + 0.05) ÷ (어두운 쪽 + 0.05)로 계산해요. 상대 휘도는 RGB 채널을 감마 해제한 뒤 0.2126, 0.7152, 0.0722 가중치로 더한 값이에요. 직접 계산하기 번거로우면 색상 대비 계산 도구에 두 색을 넣으면 돼요.

흰 배경에서 쓸 수 있는 가장 연한 회색은 무엇인가요?

본문 기준(4.5:1)으로는 #767676 이 4.54:1로 통과하는 가장 연한 회색이에요. 한 단계 연한 #777777 은 4.47:1로 미달이에요. 큰 글자(3:1)라면 #959595(2.99) 바로 진한 쪽인 #949494 정도까지 쓸 수 있어요.

큰 텍스트 기준 18pt는 몇 px인가요?

1pt는 1.333px이라 18pt는 24px, 14pt는 약 18.66px이에요. 24px 이상이거나, 굵게(bold) 처리된 18.66px 이상이면 큰 텍스트로 3:1 기준이 적용돼요. 한글은 획이 복잡해서 같은 크기라도 더 넉넉한 대비를 주는 게 좋아요.

비활성화된 버튼도 명도 대비를 지켜야 하나요?

WCAG 2.x에서 비활성(disabled) 상태의 요소는 대비 기준에서 제외돼요. 다만 사용자가 "왜 안 눌리지?"를 알아볼 수 있도록 너무 흐리게 하지 않는 게 좋아요. 로고와 순수 장식 요소도 예외예요.

다크 모드에서도 같은 기준이 적용되나요?

네, 기준은 같아요. 다만 라이트 모드 색을 그대로 반전하면 통과하던 조합이 실패하는 경우가 많아요. 예를 들어 #767676 은 흰 배경에선 통과하지만 #121212 배경에선 4.12:1로 미달이라, 다크 모드 색은 별도로 계산해야 해요.

정리

명도 대비는 본문 4.5:1, 큰 글자·UI 요소 3:1, 이 두 숫자가 핵심이에요. 흰 배경 회색은 #767676 이 경계선이고, 중간 밝기의 파랑·빨강 버튼은 흰 글자로도 미달인 경우가 많아요. 색상은 유지하고 밝기만 낮추면 브랜드 느낌을 지키면서 기준을 맞출 수 있지만, 노랑·초록 계열은 글자색을 어둡게 바꾸는 쪽이 현실적이에요.

색상 코드 변환기에 글자색과 배경색을 넣으면 대비율과 AA·AAA 통과 여부가 함께 표시되니, L을 몇 퍼센트씩 내려 가며 4.5를 넘는 지점을 찾아보세요.

#WCAG 명도 대비#색상 대비율 계산#웹 접근성 색상#4.5:1 기준#KWCAG 명도 대비#상대 휘도#접근성 회색
← 이전 글UUID v4 v7 차이, 128비트 구조와 충돌 확률을 계산으로 정리다음 글 →DB 기본키 UUID vs 자동증가 정수, UUIDv7이 인덱스에 유리한 이유