2P by GN⁺ | ★ favorite | 댓글 1개
  • CSS contrast-color()는 버튼 배경처럼 자주 바뀌는 색에 맞춰 브라우저가 검정 또는 흰색 텍스트 색을 골라, 색상 쌍을 따로 관리하는 부담을 줄임
  • color: contrast-color(var(--button-color));처럼 기준 색을 넘기면 배경색 변경에 따라 더 큰 대비를 내는 선택지가 다시 계산됨
  • 현재 Safari Technology Preview는 WCAG 2 대비 알고리듬을 사용해 #317CFF 배경에서 검정 텍스트를 고르지만, 사람의 지각으로는 흰색이 더 자연스럽게 보일 수 있음
  • 이 기능은 접근성을 자동 보장하지 않으며, 팔레트·글자 크기·글꼴 굵기·prefers-contrast 같은 디자인 검증이 여전히 필요함
  • CSS Working Group은 WCAG 3 대비 알고리듬 논의가 끝나기 전까지 기능을 검정/흰색 선택으로 좁혀 제공하며, 더 복잡한 색상 선택 도구는 이후 확장될 수 있음

contrast-color()가 줄여주는 색상 관리 부담

  • 웹사이트나 웹 앱에서 여러 배경색의 버튼을 만들 때는 배경이 어두우면 흰색 텍스트, 밝으면 검정 텍스트를 맞춰야 함
  • 기존에는 --button-color--button-text-color를 함께 정의해 색상 쌍을 관리해야 했고, 큰 프로젝트나 여러 팀이 참여하는 환경에서는 조합을 계속 정확히 유지하기 어려움
  • contrast-color()는 CSS에서 기준 색을 넘기면 브라우저가 검정 또는 흰색 중 더 대비가 큰 색을 선택함
color: contrast-color(purple);
  • 버튼에는 배경색 변수와 contrast-color()를 함께 적용할 수 있음
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
}
  • 이 방식에서는 하나의 배경색만 정의해도 텍스트 색은 브라우저가 따라 정함

상태별 색상까지 한 변수에서 파생하기

  • Relative Color Syntax를 함께 쓰면 기본 버튼 색에서 hover 색을 만들고, 각 상태의 텍스트 색도 contrast-color()로 계산할 수 있음
:root {
  --button-color: purple;
  --hover-color: oklch(from var(--button-color) calc(l + .2) c h);
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  text-box: cap alphabetic; /* vertically centers the text */
}
button:hover {
  background-color: var(--hover-color);
  color: contrast-color(var(--hover-color));
}

WCAG 2 기반 계산이 만드는 어색한 결과

  • contrast-color()접근성 대비 문제를 자동으로 모두 해결하지 않음
  • 어떤 배경색은 검정이나 흰색 어느 쪽과도 충분한 대비를 만들지 못할 수 있어, 디자이너·개발자·테스터의 확인이 필요함
  • 2025년 5월 기준 Safari Technology Preview 구현은 WCAG 2 알고리듬을 사용함
    • #317CFF 파란색 배경에서 WCAG 2는 검정 텍스트 대비를 5.45:1로 계산함
    • 같은 배경에서 흰색 텍스트 대비는 3.84:1로 계산됨
    • contrast-color()는 더 큰 숫자인 검정을 선택함
  • 사람의 지각으로는 이 조합에서 흰색이 더 나아 보일 수 있으며, WCAG 2 색 대비 알고리듬은 오래전부터 비판을 받아왔음

APCA와 WCAG 3에서 이어지는 대비 알고리듬 논의

  • Accessible Perceptual Contrast Algorithm(APCA)WCAG 3에 포함될 수 있는 후보 중 하나임
  • APCA Contrast Calculator#317CFF 배경에서 검정 텍스트를 Lc 38.7, 흰색 텍스트를 Lc -70.9로 평가함
    • APCA 점수에서 음수는 텍스트가 배경보다 밝다는 뜻임
    • 대비 크기는 부호를 제외한 38.7과 70.9로 비교함
    • 이 결과에서는 흰색 텍스트의 대비가 더 높음
  • APCA는 단순 수학 비율보다 지각 기반 대비를 중시하며, 사람이 색상과 밝기에 따라 대비를 선형적으로 인식하지 않는다는 점을 반영함
  • CSS 표준은 아직 기존 알고리듬 사용을 요구하지만, 향후 다른 대비 알고리듬이 추가될 수 있다는 주석을 포함함
  • WCAG 3에서 어떤 알고리듬을 사용할지, 후보 알고리듬의 라이선스를 어떻게 다룰지는 계속 논의 중임

실제 디자인에서 함께 확인할 조건

  • contrast-color()가 더 나은 색을 고르는 것과 충분한 대비를 보장하는 것은 다름
  • 특히 중간 톤 색상에서는 알고리듬별 결과 차이가 커질 수 있음
  • 더 높은 대비를 원하는 사용자를 위해 prefers-contrast 미디어 쿼리를 함께 사용할 수 있음
@media (prefers-contrast: more) {
  /* styling with more contrast */
}
  • 예시에서는 나무 묘목장 웹사이트의 브랜드 버튼 배경색으로 #2DAD4E를 사용함
    • APCA 기준 흰색 텍스트와의 대비는 Lc -60.4임
    • 이 값이 충분한지는 텍스트 크기와 글꼴 굵기에 따라 달라짐
    • APCA는 WCAG 2의 비율 대신 Lc -108부터 106까지의 단일 점수를 사용함
  • APCA Readability Criterion은 Bronze, Silver, Gold 수준의 목표를 제시해 글자 크기와 굵기 선택에 도움을 줌
  • 예시 계산에서는 흰색 텍스트와 #2DAD4E 조합이 24px 텍스트에 font-weight 400 이상이면 동작하고, font-weight 300을 쓰려면 최소 41px가 필요함
  • 더 높은 대비를 요청한 사용자에게는 라이트 모드와 다크 모드에 별도 색을 지정할 수 있음
    • 라이트 모드: 더 어두운 초록과 흰색 텍스트, Lc -76.1
    • 다크 모드: 더 밝은 초록과 검정 텍스트, Lc 75.2
--button-color: #2DAD4E; /* brand green background */
@media (prefers-contrast: more) {
  @media (prefers-color-scheme: light) {
    --button-color: #419543; /* darker green background */
  }
  @media (prefers-color-scheme: dark) {
    --button-color: #77CA8B; /* lighter green background */
  }
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  font-size: 1.5rem; /* 1.5 * 16 = 24px at normal zoom */
  font-weight: 500;
}
  • 현재 환경에서는 WCAG 2 알고리듬이 contrast-color()를 구동하므로 이 예시 웹사이트에는 사용하기 어려울 수 있음
  • 브랜드 색이 더 어두운 초록이고 검정/흰색 선택이 올바르게 나오는 프로젝트에서는 지금도 유용할 수 있음
  • enabled/disabled, light/dark mode, prefers-contrast처럼 여러 상태나 옵션의 색을 정의할 때 contrast-color()가 특히 도움이 됨

검정/흰색 선택으로 먼저 제한된 이유

  • 현재 contrast-color()는 Safari Technology Preview 122에 있던 color-contrast()보다 기능이 크게 단순화됨
  • CSS Working Group은 WCAG 3 대비 알고리듬 논의가 끝나지 않은 상태에서, 우선 기준 색과 대비되는 검정 또는 흰색만 고르는 도구로 진행함
  • 선택지를 검정/흰색으로 고정하면 나중에 WCAG 2 알고리듬이 바뀌어도 웹사이트가 깨질 가능성이 줄어듦
  • 향후에는 사용자 정의 색상 목록에서 브라우저가 선택하거나, 원하는 대비 수준을 지정하는 더 강력한 도구가 나올 수 있음
  • contrast-color()는 텍스트 색뿐 아니라 배경, 테두리 등 색을 지정하는 여러 속성에 사용할 수 있음

댓글과 토론

Hacker News 의견들
  • 이 문제를 위해 색상 쌍이 설계 단계부터 단순하고 예측 가능한 WCAG/APCA 대비를 갖도록 팔레트를 만드는 도구를 작업 중임. 데스크톱에서는 기능이 더 많음
    https://www.inclusivecolors.com/
    한 가지 방식은 여러 색상의 견본을 grade 100(밝음)부터 grade 900(어두움)까지 만들고, 모든 grade 700 색상이 grade 100 색상과 대비되고, grade 800 색상이 grade 200 색상과 대비되도록 명도를 정하는 것임
    그러면 red-700 대 gray-100, green-800 대 yellow-200 같은 조합은 매번 확인하지 않아도 대비가 보장됨
    Contrast 메뉴에서는 더 정확하게 설계된 APCA 알고리즘이 WCAG보다 얼마나 엄격한지도 볼 수 있음. 특히 밝은 배경 위 어두운 색에서는 APCA가 훨씬 엄격하므로 다크 테마에는 WCAG를 쓰면 안 된다고 봄
    Examples 메뉴에서 Tailwind와 IBM Carbon 색상 팔레트를 보면, 수작업 팔레트의 견본들이 grade에 따라 채도와 색상을 비선형적으로 바꾸는 것도 확인 가능함. 흰색/검은색 중 무엇이 가장 잘 대비되는지 자동으로 고르는 건 비교적 단순하지만, 더 의도적이고 브랜드화된 팔레트에서는 단순히 명도 성분만 이동해 색을 생성할 수 없어 훨씬 열린 문제로 남음

  • LCH를 쓰면 이에 가까운 걸 할 수 있음
    --text: lch(from var(--bg) calc((49.44 - l) * infinity) 0 0);
    출처: https://til.jakelazaroff.com/css/swap-between-black-and-whit...

    • LCH도 훌륭하지만 OKLCH가 더 낫다고 봄
      https://evilmartians.com/chronicles/oklch-in-css-why-quit-rg...
      이 글이 이 주제에 대한 관점을 크게 바꿔 줬고, 정말 대단한 도구였음. 디자이너 친구들이 oklch를 전혀 모른다는 데 놀랐고, 이건 한 범주의 문제를 통째로 해결해 줌
    • 이런 식으로 매개변수를 받아 수정할 수 있는 콜백 스타일 CSS 함수는 처음 봤음. 흥미로운데, 이런 예가 또 있는지 아니면 lch에만 있는지 궁금함
    • Lea Verou가 이런 우회 방식에 대해 좋은 글을 썼음: https://lea.verou.me/blog/2024/contrast-color/
  • 장단점을 잘 정리한 글임. 단순한 사이트를 만드는 경우라면 올바른 대비를 쉽게 확보하는 탄탄한 방법임
    다만 WCAG 준수가 필요한 프로덕션 규모라면 이 방식은 피하고 제대로 된 시맨틱 토큰 계층을 쓰는 편이 좋음. 시맨틱 토큰은 개발 주기를 빠르게 하고, 단순히 전경색을 검정/흰색으로 바꾸는 것보다 시각적으로 더 나은 방식으로 적절한 대비율을 보장해 줌
    시맨틱 토큰 계층의 장점은 테마화가 매우 쉽다는 데 있음. 적은 추가 비용으로 라이트/다크 테마를 얻을 수 있고, 브랜드 색상이 WCAG2와 충돌하는 경우에는 별도의 WCAG2/APCA 접근성 테마도 만들 수 있음
    이 분야가 내 전문 영역에 가까움. Figma에서 variables/tokens 스트림을 맡고 있고, Figma와 Atlassian의 다크 모드 구현에도 참여했음. 토큰, 테마, 접근 가능한 색상에 대해 질문이 있으면 답할 수 있음

    • 시맨틱 토큰이 무엇을 뜻하는지 궁금함
      내가 작업하는 큰 프로젝트도 상대 색상과 대비 색상 때문에 CSS-in-JS를 쓰게 됐음
      이런 기능이 다가오는 건 반갑고, 몇 년 안에 널리 쓸 수 있게 되길 기대함
    • 동의하지 않는 건 아니고 오히려 전적으로 동의하지만, 뒤쪽 2/3는 똑똑해 보이려는 의미 없는 말처럼 들림. 사실이 아니라는 건 아니지만 말이 너무 쏟아진 느낌임
      이 기능은 마음에 들지만, 기업 사이트나 애플리케이션에서는 결과가 무엇이 될지 통제할 수 없기 때문에 이 함수에 의존하고 싶지 않음. WebKit이 나중에 버그를 고치거나 뭔가를 바꿔서 결과 색상이 원치 않는 색으로 달라질 수도 있음
  • “큰 프로젝트에서는 버튼을 출시 전에 누가 한 번 보면 안 되나? 아니면 어두운 버튼에 검은 텍스트를 절대 쓰지 말라고 팀 전체에 강제하면 되지 않나?”라는 생각이 듦
    지각적 대비와 수학적 대비 차이는 흥미로웠고 몰랐던 내용이라 워크플로에 통합해 볼 예정임

    • 물론 볼 수는 있지만, 그러면 출시 전 회귀 테스트 주기가 몇 주에서 몇 달까지 길어질 수 있음. 큰 프로젝트에는 버튼이 수천 개 이상 있을 수 있고, 그중 많은 버튼은 특정 설정이 켜졌거나 복잡한 흐름에서 특정 옵션을 선택했을 때만 보임
    • 지각적 대비 계산이 필요하다면 APCA 알고리즘을 읽어보는 게 좋음
  • 대비 색상을 브라우저 벤더가 결정해야 한다는 데는 아직 확신이 없음. 항상 맞거나 예측 가능하지는 않을 것 같음
    모든 브라우저에서 확정적이고 결정적인 표준이 될 수 있을까? 이 함수는 오히려 설계 단계에서 UX 팀을 돕는 도구처럼 느껴짐

    • 글에서는 표준이 사용할 계산 방식을 명시한다고 함
    • 여기서 “선택한다”는 표현은 좀 이상함. 색상을 계산하는 알고리즘이 있는 것임
    • https://news.ycombinator.com/item?id=44015980를 참고하면, APCA의 버튼 예시를 혼동해서 생긴 잘못된 부분을 덜어내고 나면 100% 맞는다는 점이 더 분명해짐
      다만 일관적이진 않음. 예를 들어 L* 50 배경은 흰색 전경과 검은색 전경이 거의 비슷하게 적합할 수 있고, 이때는 미적 원칙이 개입함
      이를 명확히 해서 100% 신뢰 가능하게 하려면, 대비 K와 배경색 C가 주어졌을 때 더 어두운 색과 더 밝은 색이 모두 가능하다면 C의 L*가 60 이상인지 보고 더 밝은 색을 고르면 됨. 그러면 100% 정확하고 일관적임
  • 예전에 시스템 색상이 멋지던 시절에 시스템 색상 스타일을 만든 적이 있음. 보기에는 좋았지만 대비가 어떻게 나올지는 알 수 없었음
    예를 들어 buttonFace와 buttonText 같은 이름이 있었지만, 실제로는 의미가 없어졌음. 누가 getComputedStyle을 가져와 대비를 계산하는 JavaScript를 만들어 줬고, 대비가 부족하면 두 번째 후보 색을 쓰거나 text-shadow로 텍스트 주변의 후광을 충분히 어둡게/밝게 만드는 방식으로 대체했음
    https://i.sstatic.net/18bQt.png
    계산식은 잊었지만, 생각해 보면 RGB 세 값의 평균을 내서 비교해도 될 것 같음. 파란색에는 낮은 값이 나오고 흰색 텍스트를 선호하게 될 것임

  • 최소한 라이트/다크 테마에서 active, focus, hover, link, visited 의사 클래스와 그 조합에 적합한 색을 알 수 있으면 좋겠음. Material UI는 여기에 disabled, before, after도 추가함

  • 오래전에 비슷한 주제로, 배경색이 주어졌을 때 텍스트 색을 검정 또는 흰색 중 고르는 동영상 튜토리얼을 만든 적이 있음
    내 해법은 아주 단순했음. 색상을 회색조로 변환한 뒤 검정/흰색과 비교했음. 재미있는 프로젝트였지만 영상 제작은 잘하지 못함
    https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR
    경고: 영상은 포르투갈어임

    • 마침 같은 레벨의 다른 댓글이 색공간 수식으로 정확히 그 일을 하는 방법을 제시했음
      https://news.ycombinator.com/item?id=44015990
      영상은 괜찮아 보임. 포르투갈어를 몰라서 말한 내용은 판단할 수 없지만 코드는 좋아 보임
  • 어차피 색상표의 모든 색을 고르는데, 처음부터 대비되는 버튼 텍스트 색을 고르는 것보다 이게 왜 더 쉬운지 모르겠음
    배경색은 제각각 고를 수 있을 정도로 자유롭지만 정작 대비되는 전경색은 못 고르는 비정상적인 팀을 돕는 기능처럼 보임
    진짜 고쳐야 할 건 이미지나 다양한 배경 위에 텍스트가 올라가는 경우임. 예를 들어 스크롤되는 배경 위에 sticky/fixed 텍스트가 있고 항상 보여야 하는 상황인데, 이 기능은 전혀 도움이 되지 않음
    그래서 이 기능은 매우 의심스러운 상황에서만 겨우 도움이 될 수 있고, 그것도 새로운 동사까지 만들어야 했으며, 기능도 빈약해서 검정/흰색만 고르고, 대비 선택 알고리즘도 최악임. 지각적 대비가 가장 큰 선택지를 고르지 않음

    • 어떤 도구가 유용한 상황을 직접 만나보지 못했다는 이유만으로 곧바로 배척하는 건 제한적인 태도임
      많은 웹사이트는 최종 사용자가 색상을 고르게 하거나, 사용자가 제공한 자산에서 색상을 자동으로 추출함. 접근성을 신경 쓰는 사이트라면 사용자가 접근 불가능한 경험을 만들지 않도록 보통 대비 색상을 계산함
      이런 내장 CSS 도구는 더 나은 경험을 만들고 싶은 사람을 방해하지 않으면서도, 더 많은 사이트가 기본적인 접근성을 제공하도록 장려할 수 있음
      npm의 contrast-color 패키지처럼 더 커스터마이즈 가능하면 멋지겠지만, 블로그 글에서는 왜 흰색/검은색부터 시작했는지와 나중에 알고리즘을 바꿀 의도가 있음을 설명함
      [1] 예시: https://coolors.co/8fbfe0-7c77b9-1d8a99-0bc9cd-14fff7
    • 이들은 명시적으로 WCAG 2 알고리즘을 따른다고 했고, WCAG 3에서 이 문제가 수정될 수 있다고도 말함. 더 나은 알고리즘이 표준화되면 앞으로 쉽게 전환할 수 있다고 함
  • 이걸 빌드 시점에 처리하는 좋은 대안이 있을까? SASS, Tailwind 같은 것 위에서 동작하는 방식이면 좋겠음
    이 기능이 널리 사용 가능해지려면 시간이 걸릴 것이고, 모든 플랫폼에서 같은 방식이나 올바른 방식으로 구현될지도 의심스러움