WCAG 성공 기준 · Level AAA
WCAG 2.4.9: 링크 목적 (링크만으로)
WCAG 2.4.9는 주변 문맥에 의존하지 않고, 링크 텍스트만으로도 모든 링크의 목적을 파악할 수 있어야 한다고 요구합니다. 이보다 더 엄격한 AAA 기준은 모든 사용자, 특히 링크 단위로 탐색하는 스크린 리더 사용자들이 페이지 전체를 읽지 않고도 링크가 어디로 연결되는지 이해할 수 있도록 보장합니다.
- Level AAA
이 규칙의 의미
\nWCAG 2.4.9 — 링크 목적(링크만으로)은 WCAG 2.1 및 2.2에서 AAA 레벨 성공 기준이다. 이 기준은 각 링크의 목적이 링크 텍스트만으로 파악될 수 있어야 한다고 요구한다. 제목, 문단, 표 셀과 같은 주변 문맥에서 링크 목적을 유추하는 것을 허용하는 AA 레벨 기준인 WCAG 2.4.4(링크 목적 — 문맥 내)와 달리, 2.4.9는 훨씬 더 엄격하다. 링크 텍스트 자체가 완전히 자기 설명적이어야 하며, 주변 콘텐츠에 전혀 의존해서는 안 된다.
\n이 기준은 <a> 요소로 만든 모든 하이퍼링크뿐 아니라 접근 가능한 이름을 가지고 링크처럼 동작하는 모든 인터랙티브 요소에 적용된다. 여기에는 이미지 링크(이미지의 alt 속성이나 aria-label이 접근 가능한 이름을 제공하는 경우), 버튼 스타일의 링크, SVG 기반 링크가 포함된다. 링크의 접근 가능한 이름은 눈에 보이는 텍스트, aria-label, aria-labelledby, 또는 이미지 대체 텍스트에서 계산되며, 이 이름만으로 링크가 어디로 이동하는지 또는 무엇을 하는지를 전달해야 한다.
통과로 인정되는 경우: 사용자가 페이지의 다른 어떤 것도 읽지 않고 링크 텍스트만 읽었을 때, 그 링크의 목적지나 기능을 자신 있게 이해할 수 있다면 해당 링크는 2.4.9를 통과한다. 예를 들어, "2024 연간 접근성 보고서 다운로드(PDF, 2.4 MB)"라는 링크는 관련 정보가 모두 링크 텍스트 안에 포함되어 있으므로 통과한다. "전체 기사 읽기: 접근 가능한 링크 텍스트 작성 방법"이라는 링크도 통과한다. alt='요금제 보기'가 있는 이미지 링크 역시 alt 텍스트가 충분히 설명적이기 때문에 통과한다.
실패로 인정되는 경우: 링크 텍스트가 "여기를 클릭", "더 읽기", "자세히 보기", "더 알아보기", "이것", "여기"처럼 문맥이 있어야만 의미가 있는 표현인 경우 이 기준을 충족하지 못한다. 마찬가지로, alt 속성이 비어 있거나 없는 이미지를 감싸는 링크는 접근 가능한 이름이 없으므로 실패한다. aria-label 또는 aria-labelledby를 사용했지만 계산된 접근 가능한 이름이 여전히 모호한 링크도 실패한다.
공식 예외: WCAG는 한 가지 예외를 명시한다. 링크의 목적이 장애가 있는 사용자뿐 아니라 모든 사용자에게 의도적으로 모호한 경우이다. 예를 들어, 미스터리 게임에서 "진행"이라고만 적힌 티저 링크가 있고, 이 모호함이 의도된 디자인인 경우, 이 모호함이 모든 사용자에게 동일하게 적용된다면 실패로 간주되지 않는다. 이 예외는 매우 좁게 정의되며, 부적절한 링크 텍스트를 정당화하는 구멍으로 사용되어서는 안 된다.
\n\n왜 중요한가
\n의미 있는 링크 텍스트는 웹사이트가 할 수 있는 가장 영향력 있는 접근성 개선 중 하나이다. 모호한 링크 텍스트의 영향을 가장 직접적으로 받는 집단은 스크린 리더 사용자, 키보드로 탐색하는 사용자, 인지 장애가 있는 사용자, 음성 제어 소프트웨어 사용자이다.
\n대개 시각장애인 또는 심각한 저시력인 스크린 리더 사용자는 페이지를 탐색할 때 모든 링크 목록을 불러오는 방식으로 이동하는 경우가 많다. NVDA, JAWS, VoiceOver와 같은 인기 있는 스크린 리더는 모두 이 기능을 제공하며, 각 링크의 접근 가능한 이름을 추출해 독립된 목록으로 보여준다. 링크가 "여기를 클릭", "더 읽기", "자세히", "상세"처럼 되어 있으면 이 목록은 서로 구분되지 않는 의미 없는 항목들의 나열이 된다. 사용자는 각 링크 주변의 콘텐츠를 일일이 읽어보지 않고는 어떤 링크를 활성화해야 할지 알 수 없는데, 이는 특히 수십 개의 링크가 있는 페이지에서 매우 느리고 답답하며, 때로는 사실상 불가능한 작업이다.
\n세계보건기구에 따르면 전 세계적으로 약 22억 명이 어떤 형태로든 시각 장애를 가지고 있으며, 이 중 최소 10억 명은 예방이 가능했거나 아직 해결되지 않은 상태의 질환을 가지고 있다. 시각 장애가 없는 사용자 중에서도 스위치 액세스나 음성 내비게이션(Dragon NaturallySpeaking과 같은 도구 사용)에 의존하는 운동 장애 사용자는 링크 텍스트가 설명적일수록 큰 이점을 얻는다. 링크 이름을 말하거나 선택하는 것만으로 바로 링크를 활성화할 수 있기 때문이다. 주의력 결핍, 기억력 손상, 읽기 어려움 등을 가진 인지 장애 사용자 그룹 역시 명확한 링크 레이블 덕분에 인지적 부담이 줄어들고 문맥을 반복해서 읽을 필요가 줄어든다는 이점을 얻는다.
\n현실적인 사례를 생각해 보자. 한 터키 시민이 공공 병원 웹사이트를 방문해 진료 예약을 하려고 한다. 페이지에는 "Randevu Al"(예약하기)라는 문구만 들어 있는 서비스 버튼이 세 개 있고, 링크 텍스트에는 그 외의 문맥이 없다. 시각장애 사용자가 스크린 리더의 링크 목록을 열면 "Randevu Al", "Randevu Al", "Randevu Al"이라는 서로 구분되지 않는 세 개의 옵션만 보인다. 이 사용자는 문맥으로 다시 돌아가 확인하지 않고는 어떤 링크가 심장내과, 어떤 링크가 일반 진료, 어떤 링크가 영상의학과인지 알 수 없다. WCAG 2.4.9를 충족하려면 각 링크가 "Randevu Al — Kardiyoloji", "Randevu Al — Genel Pratisyen", "Randevu Al — Radyoloji"처럼 링크 텍스트만으로 목적이 명확해지도록 작성되어야 한다.
\n접근성을 넘어, 설명적인 링크 텍스트는 SEO 측면에서도 상당한 가치를 가진다. 검색 엔진 크롤러는 페이지를 인덱싱할 때 앵커 텍스트에 가중치를 두며, 설명적인 링크는 출발 페이지와 도착 페이지 모두에 대해 관련성 신호를 향상시킨다. 일반적인 앵커 텍스트를 의미 있는 문구로 바꾸면 검색 가능성이 높아지고 이탈률이 줄어들어 모든 사용자에게 이득이 된다.
\n\n관련 Axe-core 규칙
\nWCAG 2.4.9는 자동화 도구가 의미나 의도를 판단할 수 없기 때문에 수동 테스트가 필요하다. 자동화 도구는 접근 가능한 이름의 부재는 표시할 수 있지만, 주어진 접근 가능한 이름이 충분히 설명적인지 여부는 판단할 수 없다.
\n- \n
- 수동 테스트 필요 — link-name(axe 규칙): axe-core의
link-name규칙은 접근 가능한 이름이 전혀 없는 링크를 탐지한다(예: 텍스트 콘텐츠,aria-label,aria-labelledby가 없고, 비어 있지 않은alt속성을 가진 이미지도 없는<a>요소). 이 규칙은 완전히 비어 있는 링크를 잡아내지만, 존재하는 접근 가능한 이름이 의미 있는지 여부는 평가할 수 없다. "여기"라고만 적힌 링크는 기술적으로 접근 가능한 이름이 있기 때문에 자동화된link-name규칙을 통과하지만, 그 이름이 자기 설명적이지 않기 때문에 WCAG 2.4.9에서는 실패한다. 이것이 바로 2.4.9가 수동 검토가 필요한 기준으로 표시되는 이유이다. 사람이 각 링크 레이블을 읽고, 그 레이블이 문맥 없이도 목적을 전달하는지 판단해야 한다. \n - 수동 테스트 필요 — identical-links-same-purpose: Axe-core에는 접근 가능한 이름은 동일하지만 목적지가 다른 링크 집합을 표시하는 규칙이 포함되어 있다. 이는 잠재적인 2.4.9 위반을 드러내는 휴리스틱으로, 예를 들어 서로 다른 기사로 연결되는 여러 개의 "Read More" 링크가 이에 해당한다. 그러나 이 규칙 역시 사람의 확인이 필요하다. 동일한 이름이 동일한 목적지로 연결되는 경우는 위반이 아니기 때문이다. 이 규칙은 확정적인 실패가 아니라 검토 대상 후보를 제시한다. \n
- 자동화만으로는 충분하지 않은 이유: 링크 목적을 평가하려면 자연어 이해가 필요하다. 자동화 도구는 링크의 접근 가능한 이름이 "details"라는 문자열이라는 사실은 계산할 수 있지만, 이 문자열이 목적지를 설명하지 못한다는 점은 알 수 없다. 마찬가지로, 도구는 "View"가 충분한지(아주 좁게 범위가 정의된 인터페이스에서는 충분할 수도 있다) 또는 "View the Q3 Financial Statement"가 더 나은지 판단할 수 없다. 스크린 리더 테스트와 결합된 인간의 판단만이 신뢰할 수 있는 방법이다. \n
테스트 방법
\n- \n
- 자동 스캔을 통한 기본 점검: 대상 페이지에서 axe DevTools(브라우저 확장) 또는 Lighthouse를 실행한다. axe DevTools에서
link-name규칙 위반이 있는지 확인한다. 이는 접근 가능한 이름이 전혀 없는 링크를 의미하며, 2.4.9의 확실한 실패 사례이다. 결과를 내보내고 표시된 모든 링크를 기록한다. 이 단계만으로 2.4.9 감사가 완료되는 것은 아니며, 가장 명백한 실패만 식별하는 단계이다. \n - 스크린 리더로 링크 목록 생성: NVDA가 설치된 Firefox에서 페이지를 연다. Insert + F7(NVDA의 요소 목록 단축키)을 눌러 대화 상자에서 "Links"를 선택한다. 전체 링크 레이블 목록을 검토한다. "이 목록만 읽는 사용자가 각 링크의 목적지나 기능을 이해할 수 있는가?"라고 자문해 본다. JAWS에서는 Insert + F7을 눌러 링크 목록 대화 상자를 열고, macOS의 Safari에서 VoiceOver를 사용할 때는 VO + U를 눌러 로터를 연 뒤 Links로 이동해 같은 테스트를 반복한다. 레이블이 모호하거나, 다른 목적지로 가는 링크끼리 레이블이 중복되었거나, URL 문자열만으로 구성된 링크를 모두 표시해 둔다. \n
- 키보드 탭 순서 점검: Tab 키만 사용해 페이지를 탐색한다. 포커스가 링크에 도달할 때마다, 주변 콘텐츠를 보지 말고 포커스된 요소의 눈에 보이는 텍스트만 읽거나 스크린 리더의 안내만 듣는다. 이 정보만으로 링크의 목적이 명확한지 판단한다. 링크 텍스트만으로 목적이 불분명한 모든 링크를 기록한다. \n
- 이미지 링크 점검: 눈에 보이는 텍스트 없이 이미지로만 구성된 모든 링크를 찾는다. 브라우저 개발자 도구로 각 링크를 검사해 이미지에 비어 있지 않고 설명적인
alt속성이 있는지, 또는 링크에 설명적인aria-label이 있는지 확인한다. 접근 가능한 이름 계산 결과가 의미 있는 문구가 되어야 한다. \n - 중복 링크 텍스트 점검: 페이지 HTML에서 동일한 링크 텍스트가 여러 번 등장하는지 검색한다(예: 여러 개의 "Read More" 또는 "Buy Now" 앵커). 이 링크들이 동일한 목적지로 연결되는지(허용됨), 아니면 서로 다른 목적지로 연결되는지(2.4.9 실패, 단
aria-label또는aria-labelledby로 구분해 주지 않은 경우)를 확인한다. \n - 음성 제어 테스트: Dragon NaturallySpeaking 또는 Windows Voice Access를 사용해 눈에 보이는 레이블을 말하는 방식으로 링크를 활성화해 본다. 말한 레이블이 모호해 여러 후보가 나타난다면(예: "Click Read More"라고 말했을 때 여러 링크가 강조 표시되는 경우), 이는 2.4.9와 관련된 실제 사용성 실패를 확인해 주는 것이다. \n
수정 방법
\n일반적인 "Read More" 링크 텍스트 — 잘못된 예
\n<!-- 2.4.9 실패: 링크 목적을 링크 텍스트만으로 파악할 수 없음 -->\n<article>\n <h3>How to Improve Your Website's Accessibility Score</h3>\n <p>Accessibility improvements can dramatically increase your user base...</p>\n <a href='/blog/improve-accessibility-score'>Read more</a>\n</article>\n일반적인 "Read More" 링크 텍스트 — 올바른 예
\n<!-- 2.4.9 통과: 링크 목적이 링크 텍스트만으로 완전히 명확함 -->\n<article>\n <h3>How to Improve Your Website's Accessibility Score</h3>\n <p>Accessibility improvements can dramatically increase your user base...</p>\n <a href='/blog/improve-accessibility-score'>\n Read more about improving your website's accessibility score\n </a>\n</article>\n\n<!-- 대안: 시각적으로는 짧은 텍스트를 보여주되, 전체 접근 가능한 이름을 제공 -->\n<a href='/blog/improve-accessibility-score'\n aria-label='Read more about improving your website's accessibility score'>\n Read more\n</a>\n\nalt 텍스트가 없는 이미지 전용 링크 — 잘못된 예
\n<!-- 2.4.9 실패: 이미지 링크에 접근 가능한 이름이 없어 스크린 리더가 URL 또는 아무것도 읽지 못함 -->\n<a href='https://accsible.com/pricing'>\n <img src='/icons/pricing.svg' alt=''>\n</a>\nalt 텍스트가 없는 이미지 전용 링크 — 올바른 예
\n<!-- 2.4.9 통과: alt 텍스트가 링크에 완전히 설명적인 접근 가능한 이름을 부여함 -->\n<a href='https://accsible.com/pricing'>\n <img src='/icons/pricing.svg' alt='View Accsible pricing plans'>\n</a>\n\n<!-- 앵커에 aria-label을 사용하는 대안 -->\n<a href='https://accsible.com/pricing' aria-label='View Accsible pricing plans'>\n <img src='/icons/pricing.svg' alt=''>\n <!-- alt=''는 장식용 이미지를 보조기술에서 숨기고, <a>의 aria-label이 이름을 제공함 -->\n</a>\n\n여러 개의 동일한 "Satın Al"(지금 구매) 링크 — 잘못된 예
\n<!-- 2.4.9 실패: 서로 다른 상품으로 연결되지만 링크 레이블이 모두 동일함 -->\n<div class='product-card'>\n <h3>Temel Plan</h3>\n <a href='/plans/basic'>Satın Al</a>\n</div>\n<div class='product-card'>\n <h3>Profesyonel Plan</h3>\n <a href='/plans/pro'>Satın Al</a>\n</div>\n<div class='product-card'>\n <h3>Kurumsal Plan</h3>\n <a href='/plans/enterprise'>Satın Al</a>\n</div>\n여러 개의 동일한 "Satın Al"(지금 구매) 링크 — 올바른 예
\n<!-- 2.4.9 통과: 각 링크가 aria-label을 통해 고유하고 설명적인 접근 가능한 이름을 가짐 -->\n<div class='product-card'>\n <h3>Temel Plan</h3>\n <a href='/plans/basic' aria-label='Temel Planı Satın Al'>Satın Al</a>\n</div>\n<div class='product-card'>\n <h3>Profesyonel Plan</h3>\n <a href='/plans/pro' aria-label='Profesyonel Planı Satın Al'>Satın Al</a>\n</div>\n<div class='product-card'>\n <h3>Kurumsal Plan</h3>\n <a href='/plans/enterprise' aria-label='Kurumsal Planı Satın Al'>Satın Al</a>\n</div>\n\n<!-- 대안: 앵커 안에 시각적으로 숨겨진 텍스트 사용 -->\n<a href='/plans/basic'>\n Satın Al <span class='sr-only'>— Temel Plan</span>\n</a>\n\n(Content truncated due to token limit — please retry this article.)
