WCAG 성공 기준 · Level AA
WCAG 1.4.13: 호버 또는 포커스 시 표시되는 콘텐츠
WCAG 1.4.13은 포인터 호버나 키보드 포커스 시 나타나는 추가 콘텐츠가 닫을 수 있고, 호버할 수 있으며, 지속되도록 할 것을 요구합니다. 이는 저시력, 운동 장애, 인지 장애가 있는 사용자가 툴팁 스타일의 콘텐츠를 예기치 않게 잃지 않고 접근하고 상호작용할 수 있도록 보장하기 위한 것입니다.
- Level AA
- Wcag
- Wcag 2 2 aa
- 지각 가능
- 접근성
이 규칙의 의미
WCAG 1.4.13은 웹에서 흔히 사용되는 상호작용 패턴을 다룹니다. 즉, 사용자가 어떤 요소 위에 포인터를 올려놓거나 키보드 포커스를 이동했을 때 콘텐츠가 보이게 되는 경우입니다. 여기에는 툴팁, 서브 메뉴, 커스텀 드롭다운 힌트, 날짜 선택기 팝오버, 그리고 호버나 포커스 이벤트에 반응해 나타나는 모든 오버레이가 포함됩니다. 이 기준은 이러한 콘텐츠가 브라우저에 의해 네이티브하게 제어되지 않을 때마다 적용됩니다(예를 들어, 네이티브 title 속성 툴팁은 예외입니다). 그리고 동시에 모두 충족되어야 하는 세 가지 핵심 요구사항을 제시합니다.
닫을 수 있어야 함(Dismissible): 사용자는 포인터 포커스나 키보드 포커스를 이동하지 않고도 추가 콘텐츠를 닫을 수 있어야 합니다. 표준 메커니즘은 Escape 키를 누르는 것입니다. 이는 사용자가 해결할 수 없는 방식으로 오버레이가 다른 페이지 콘텐츠를 가리는 것을 방지합니다. 특히 화면을 확대해 사용하고 단순히 다른 곳을 흘끗 볼 수 없는 사용자에게 매우 중요합니다.
호버 가능해야 함(Hoverable): 추가 콘텐츠가 사용자가 트리거 요소 위에 포인터를 올려놓았기 때문에 나타난 경우, 사용자는 새로 나타난 콘텐츠 위로 포인터를 옮겨도 그것이 사라지지 않아야 합니다. 툴팁이 커서가 트리거 요소를 떠나는 순간 사라진다면, 사용자는 긴 내용을 읽거나 그 안의 텍스트를 복사하거나, 그 안에 있는 링크나 컨트롤을 활성화할 수 없습니다.
지속되어야 함(Persistent): 추가 콘텐츠는 호버 또는 포커스 트리거가 제거되거나, 사용자가 그것을 닫을 때(예: Escape 키), 또는 정보가 더 이상 유효하지 않을 때까지 계속 보여야 합니다. 사용자의 포인터나 포커스가 여전히 트리거 또는 오버레이 자체에 있는 동안, 콘텐츠가 타이머나 임의의 지연 후에 사라져서는 안 됩니다.
통과하려면 이 세 가지 조건이 모두 충족되어야 합니다. 하나라도 빠지면 실패입니다. 예를 들어, 포인터가 트리거에서 툴팁 쪽으로 이동할 때 툴팁이 사라지는 경우(호버 가능하지 않음), 3초 후 자동으로 닫히는 경우(지속되지 않음), 포커스를 이동하지 않고는 닫을 수 없는 경우(닫을 수 없음) 등이 있습니다. WCAG가 명시적으로 인정한 유일한 예외는 추가 콘텐츠의 시각적 표현이 전적으로 사용자 에이전트에 의해 제어되는 경우입니다. title 속성만으로 생성되는 브라우저 네이티브 툴팁이 여기에 해당하며 예외이지만, 이들 역시 고유한 접근성 한계를 가지고 있습니다.
왜 중요한가
이 기준은 주로 표준 마우스나 키보드 상호작용을 제어하는 데 어려움을 겪는 사용자, 화면 확대에 의존하는 사용자, 정보를 느리게 처리하는 사용자에게 도움이 됩니다. 누가 영향을 받는지 이해하면 팀이 수정 작업의 우선순위를 올바르게 정하는 데 도움이 됩니다.
저시력 사용자는 종종 ZoomText나 운영체제 내장 확대기 같은 화면 확대 소프트웨어를 사용합니다. 이는 높은 배율에서 화면의 일부분만 볼 수 있다는 뜻입니다. 툴팁이 나타날 때, 그것이 화면 밖으로 부분적으로 벗어나 있을 수 있고, 사용자는 그쪽으로 화면을 패닝해야 합니다. 만약 툴팁이 포인터가 트리거를 떠나는 순간 사라진다면, 사용자는 패닝해서 읽을 수 없습니다. 세계보건기구(WHO)에 따르면 전 세계적으로 약 22억 명이 어떤 형태로든 시각 장애를 가지고 있으며, 그중 컴퓨터를 사용하는 상당수는 스크린 리더가 아니라 확대 기능에 의존합니다.
운동 장애가 있는 사용자 — 파킨슨병, 떨림, 미세 운동 제어의 제한 등을 가진 사람들을 포함 — 는 대체 포인팅 장치, 머리 포인터, 시선 추적 시스템 등을 사용할 수 있습니다. 이 사용자들에게 정밀한 포인터 제어는 어렵기 때문에, 호버 영역이 관대하지 않다면 작은 트리거 요소에서 작은 툴팁으로 포인터를 옮기면서 둘 다 벗어나지 않게 유지하는 것이 거의 불가능할 수 있습니다. 호버 가능해야 한다는 요구사항은 바로 이 문제를 직접적으로 다룹니다.
인지 장애가 있는 사용자는 읽는 속도가 느리거나 내용을 여러 번 읽어야 할 수 있습니다. 몇 초 후 자동으로 사라지는 툴팁은 이 사용자들이 정보를 충분히 흡수할 시간을 제공하지 못합니다. 또한 포커스를 이동하지 않고는 닫을 수 없는 툴팁은 이들을 혼란스러운 상호작용 상태에 가둬둘 수 있습니다.
구체적인 상황을 생각해 봅시다. 한 은행 웹사이트가 계좌 이자율 세부 정보를 작은 정보 아이콘 위에 포인터를 올렸을 때 나타나는 툴팁 안에 표시합니다. 400%로 확대해 사용하는 저시력 사용자는 한 번에 페이지의 일부만 볼 수 있습니다. 아이콘 위에 포인터를 올리면 툴팁이 나타나고, 세부 내용을 읽기 위해 포인터를 툴팁 쪽으로 옮기기 시작합니다. 그러나 툴팁이 부모 요소의 호버 상태에만 묶여 있기 때문에 즉시 사라져 버립니다. 사용자는 필수 고지 정보를 볼 수 없습니다. 이는 단순한 사용성 불편을 넘어, 규제가 있는 산업에서는 법적 접근성 장벽이 될 수 있습니다.
장애와 관련된 영향뿐 아니라, 이 기준을 올바르게 구현하면 터치와 키보드를 함께 사용하는 모든 사용자에게 일반적인 사용성을 향상시키고, “사라지는” UI 요소로 인한 지원 요청을 줄이며, 사용자와 감사인 모두에게 인터페이스 품질을 보여주는 신호가 됩니다.
관련 Axe-core 규칙
WCAG 1.4.13은 수동 테스트가 필요합니다. 이 기준은 시간 기반 및 포인터 이동 동작을 포함하므로, 자동화 도구는 위반 사항을 신뢰할 수 있게 감지할 수 없습니다. 어떤 단일 axe-core 규칙도 이 기준에 직접 대응하지는 않지만, 아래의 고려사항은 자동화가 왜 부족한지, 그리고 수동 검토 시 무엇을 살펴봐야 하는지를 설명합니다.
- 수동 테스트 필요 — 호버 동작: 자동 스캐너는 특정 시점의 DOM과 CSSOM을 검사할 뿐입니다. 트리거 요소에서 새로 렌더링된 툴팁 쪽으로 포인터를 이동시키고, 툴팁이 유지되는지 관찰하는 시뮬레이션은 할 수 없습니다. 이론적으로 도구는 CSS
:hover의사 클래스가 부모의 호버가 해제될 때 자식 요소를 숨기는지 감지할 수 있지만, 포인터 경로를 시뮬레이션하지 않고는 의도적인 닫기 동작과 호버 가능 요구사항의 실패를 구분할 수 없습니다. - 수동 테스트 필요 — Escape로 닫기: Escape 키를 눌렀을 때 오버레이가 닫히는지 감지하려면, 현재 axe-core 규칙 집합을 넘어서는 JavaScript 이벤트 시뮬레이션이 필요합니다. Axe는 팝업에 ARIA 역할이 없거나
aria-expanded속성이 없는 경우를 표시할 수 있지만, Escape에 대한 keydown 리스너가 닫기 함수에 연결되어 실제로 요소를 숨기는지까지는 검증할 수 없습니다. - 수동 테스트 필요 — 지속성 / 자동 닫힘: 3초 후
setTimeout호출로 스스로를 숨기는 툴팁은, 그 시간 창 안에서 수행된 정적 스캔에서는 완전히 유효해 보일 것입니다. 오버레이를 시간에 따라 지켜보거나 JavaScript 소스를 검토하는 테스터만이 자동 닫힘 타이머를 위반으로 식별할 수 있습니다. - 수동 점검과 함께 실행할 보완적인 axe 규칙: 1.4.13을 직접 테스트하지는 않지만,
aria-tooltip-name(툴팁에 접근 가능한 이름이 있는지 확인),color-contrast(툴팁 텍스트가 읽기 쉬운지 확인),focus-visible(포커스된 트리거가 시각적으로 식별 가능한지 확인) 같은 규칙을 실행하면, 1.4.13 실패의 영향을 증폭시키는 관련 문제를 드러낼 수 있습니다.
테스트 방법
- 자동화된 기본 스캔: 호버/포커스 트리거 콘텐츠가 있는 페이지에서 axe DevTools 또는 Lighthouse를 실행합니다. 툴팁 역할, 대비, 포커스 가시성과 관련해 표시된 문제를 확인합니다. 이것이 1.4.13 준수를 확인해 주지는 않지만, 기본선을 설정합니다. 이후 수동 단계에서 대상으로 삼을 수 있도록 어떤 요소가 오버레이 콘텐츠를 트리거하는지 기록합니다.
- 모든 호버/포커스 트리거 콘텐츠 식별: 페이지를 스크롤하면서 모든 인터랙티브 요소 위에 체계적으로 포인터를 올려봅니다. 아이콘 버튼, 추가 설명이 있는 링크, 폼 필드 힌트, 내비게이션 항목, 데이터 테이블 헤더, 차트 데이터 포인트 등을 포함합니다. 추가 콘텐츠가 나타나는 각 요소를 목록으로 만듭니다.
- 호버 가능 요구사항 테스트: 식별된 각 트리거에 대해, 그 위에 포인터를 올려 오버레이를 표시한 다음, 트리거 요소에서 오버레이 콘텐츠 자체로 포인터를 천천히 이동합니다. 이 이동 내내 오버레이는 계속 보여야 합니다. 포인터가 도달하기 전에 사라지면 이 기준은 실패입니다.
- 닫을 수 있는지 테스트: 오버레이가 보이는 동안(호버 또는 키보드 포커스로 트리거된 경우), Escape 키를 누릅니다. 오버레이는 닫혀야 합니다. 닫히지 않으면 기준은 실패입니다. 이 테스트는 포인터가 여전히 트리거 위에 있을 때와, 포인터가 오버레이 위에 있을 때 모두 수행합니다.
- 지속성 요구사항 테스트: 오버레이를 트리거한 뒤, 포인터를 트리거나 오버레이 위에 둔 채 최소 10–15초 동안 그대로 둡니다. 이 시간 동안 오버레이는 계속 보여야 합니다. 사용자 행동 없이 페이드아웃되거나, 타임아웃되거나, 사라지면 기준은 실패입니다.
- 키보드 전용 테스트: 키보드만 사용해 페이지를 Tab 키로 이동합니다. 포커스가 추가 콘텐츠를 표시하는 트리거에 도달했을 때, (a) 콘텐츠가 나타나는지, (b) Escape를 누르면 닫히는지, (c) 포커스가 트리거에 머무는 동안 콘텐츠가 스스로 사라지지 않는지 확인합니다. NVDA+Firefox, JAWS+Chrome, VoiceOver+Safari를 사용해 스크린 리더가 이 콘텐츠를 올바르게 노출하는지도 확인합니다.
- 화면 확대 테스트: 브라우저 줌을 400%로 설정하거나 OS 수준 확대를 활성화합니다. 호버 테스트를 반복합니다. 툴팁에 도달하기 위해 뷰포트를 패닝해야 하는 사용자가, 툴팁이 사라지지 않고 그렇게 할 수 있는지 확인합니다.
- JavaScript 소스 검토: 코드베이스에서 오버레이 숨김 로직과 관련된
setTimeout,mouseleave,mouseout,blur이벤트 핸들러를 검색합니다. 포인터가 오버레이 위에 있거나 트리거가 포커스를 유지하는 동안 숨김 로직이 실행되지 않는지, 자동 닫힘 타이머가 설정되어 있지 않은지 확인합니다.
수정 방법
mouseleave 시 사라지는 CSS 전용 툴팁 — 잘못된 예
<!-- Tooltip only shown via CSS :hover on parent; disappears as soon as
the pointer moves off the trigger toward the tooltip text -->
<span class='tip-wrapper'>
Info
<span class='tooltip'>This is the tooltip content.</span>
</span>
<!-- CSS (illustrative) -->
<!--
.tooltip { display: none; }
.tip-wrapper:hover .tooltip { display: block; }
-->
mouseleave 시 사라지는 CSS 전용 툴팁 — 올바른 예
<!-- Correct: tooltip is also shown when the pointer is over the tooltip itself,
and the gap between trigger and tooltip is covered so pointer movement
does not accidentally dismiss the overlay. -->
<span class='tip-wrapper'>
Info
<span class='tooltip' role='tooltip' id='tip1'>This is the tooltip content.</span>
</span>
<!-- CSS (illustrative) -->
<!--
.tooltip { display: none; position: absolute; }
.tip-wrapper:hover .tooltip,
.tooltip:hover { display: block; }
/* Use padding or a transparent pseudo-element bridge between trigger and tooltip */
-->
Escape 키로 닫을 수 없는 JavaScript 툴팁 — 잘못된 예
<button aria-describedby='tip2' data-tooltip='Account balance details'>
Balance
</button>
<div id='tip2' role='tooltip' hidden>Account balance details</div>
<script>
// Only mouseenter/mouseleave — no keyboard or Escape handling
document.querySelector('button').addEventListener('mouseenter', () => {
document.getElementById('tip2').removeAttribute('hidden');
});
document.querySelector('button').addEventListener('mouseleave', () => {
document.getElementById('tip2').setAttribute('hidden', '');
});
</script>
Escape 키로 닫을 수 없는 JavaScript 툴팁 — 올바른 예
<button aria-describedby='tip2' data-tooltip='Account balance details'>
Balance
</button>
<div id='tip2' role='tooltip' hidden>Account balance details</div>
<script>
const btn = document.querySelector('button');
const tip = document.getElementById('tip2');
function showTip() { tip.removeAttribute('hidden'); }
function hideTip() { tip.setAttribute('hidden', ''); }
// Show on hover and focus
btn.addEventListener('mouseenter', showTip);
btn.addEventListener('focus', showTip);
// Hide only when pointer leaves BOTH trigger AND tooltip
btn.addEventListener('mouseleave', (e) => {
// Short delay allows pointer to reach the tooltip
setTimeout(() => {
if (!tip.matches(':hover') && !btn.matches(':hover')) hideTip();
}, 100);
});
tip.addEventListener('mouseleave', () => {
if (!btn.matches(':hover')) hideTip();
});
// Hide on blur (keyboard)
btn.addEventListener('blur', hideTip);
// Dismissible via Escape key — required by 1.4.13
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !tip.hidden) hideTip();
});
</script>
setTimeout으로 자동 닫히는 툴팁 — 잘못된 예
<button id='info-btn'>More info</button>
<div id='tip3' role='tooltip' hidden>Here is the additional information for this field.</div>
<script>
document.getElementById('info-btn').addEventListener('mouseenter', () => {
const t = document.getElementById('tip3');
t.removeAttribute('hidden');
// Violation: auto-dismisses after 3 seconds regardless of user state
setTimeout(() => t.setAttribute('hidden', ''), 3000);
});
</script>
setTimeout으로 자동 닫히는 툴팁 — 올바른 예
<button id='info-btn' aria-describedby='tip3'>More info</button>
<div id='tip3' role='tooltip' hidden>Here is the additional information for this field.</div>
<script>
const btn2 = document.getElementById('info-btn');
const tip3 = document.getElementById('tip3');
// No setTimeout — tooltip persists until user removes hover/focus or presses Escape
function show() { tip3.removeAttribute('hidden'); }
function hide() {
setTimeout(() => {
if (!tip3.matches(':hover') && !btn2.matches(':hover') && document.activeElement !== btn2) {
tip3.setAttribute('hidden', '');
}
}, 100);
}
btn2.addEventListener('mouseenter', show);
btn2.addEventListener('focus', show);
btn2.addEventListener('mouseleave', hide);
btn2.addEventListener('blur', hide);
tip3.addEventListener('mouseleave', hide);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') tip3.setAttribute('hidden', '');
});
</script>
자주 발생하는 실수
- 트리거와 툴팁 사이의 간격을 메우지 않은 채 CSS
:hover만 사용하는 경우: 트리거 요소와 툴팁 컨테이너 사이에 1–2px의 간격만 있어도, 그 사이로 포인터를 이동할 때 호버 상태가 해제되어 사용자가 툴팁에 도달하기 전에 툴팁이 숨겨집니다. 투명한 의사 요소나 겹치는 패딩을 사용해 이 간격을 메우십시오. - 포인터가 툴팁으로 이동했는지 확인하지 않은 채 트리거의
mouseleave에 숨김 로직을 바인딩하는 경우: 목적지가 툴팁 자체라 하더라도, 커서가 트리거를 떠나는 즉시 툴팁이 사라집니다. 숨기기 전에 항상tip.matches(':hover')를 확인하거나, 짧은 디바운스 지연을 사용하십시오. - mouseenter와 mouseleave에만 이벤트를 연결하고 focus와 blur 이벤트를 잊는 경우: 키보드만 사용하는 사용자가 트리거로 Tab 이동했을 때, 마우스 이벤트만 처리된다면 툴팁을 전혀 볼 수 없으며, 관련 정보는 마우스 없이는 완전히 접근 불가능해집니다.
- 클릭으로 닫을 수 있다고 가정하고 Escape 키 리스너를 추가하지 않는 경우: 키보드 사용자와 화면 확대 사용자는 오버레이에서 “클릭해서 벗어나기”가 쉽지 않습니다. Escape는 이 기준에서 기대되며 요구되는 닫기 메커니즘입니다.
- Escape 리스너를
document가 아니라 트리거 요소에만 두는 경우: 사용자가 포커스를 툴팁이나 다른 요소로 옮기면, 트리거에 한정된 리스너는 실행되지 않습니다. Escape 핸들러는 오버레이가 열려 있을 때 항상 키 이벤트를 받는 document나 공통 상위 요소에 있어야 합니다. - 고정된 시간 후 툴팁을 자동으로 닫기 위해
setTimeout을 사용하는 경우: 포인터가 여전히 트리거나 툴팁 위에 있거나, 트리거가 여전히 키보드 포커스를 가지고 있는 동안 실행되는 모든 타이머 기반 닫기 동작은 지속성 요구사항을 직접적으로 위반합니다. 호버/포커스 트리거 오버레이에서 모든 자동 닫힘 타이머를 제거하십시오. title속성을 커스텀 스타일로 대체해 툴팁 가시성을 구현하는 경우: 네이티브title툴팁을 제거하고 커스텀 버전으로 교체하는 개발자는 1.4.13의 세 가지 요구사항을 모두 직접 구현해야 합니다. 브라우저 네이티브 툴팁에 대한 예외는 동일한 패턴을 자바스크립트로 재현한 커스텀 구현에는 적용되지 않습니다.- 400% 줌에서 화면 확대 테스트를 하지 않는 경우: 일반 줌에서는 접근 가능해 보이는 툴팁도, 높은 줌에서는 화면 밖으로 부분적으로 벗어나 패닝이 필요할 수 있습니다. 그리고 사용자가 툴팁에 도달하기 전에 툴팁이 사라진다면, 100% 줌에서 통과한 테스트가 실제 사용 환경에서는 실패하게 됩니다.
- 툴팁 컨테이너에
pointer-events: none을 적용하는 경우: 이 CSS 속성은 포인터가 툴팁 위에 있는 것으로 간주되는 것을 완전히 막아, 다른 로직과 무관하게 호버 가능 요구사항을 만족하는 것을 불가능하게 만듭니다. 사용자가 상호작용해야 하거나, 단지 호버 상태를 유지해 보여야 하는 툴팁에는 절대pointer-events: none을 사용해서는 안 됩니다. - ARIA
role='tooltip'만으로 준수가 충족된다고 보는 경우:role='tooltip'과aria-describedby를 추가하는 것은 스크린 리더 접근성에 중요하지만, 문제의 다른 층위를 다루는 것입니다. 이 ARIA 속성들은 콘텐츠를 자동으로 닫을 수 있게, 호버 가능하게, 지속되게 만들지 않습니다. 상호작용 동작은 여전히 명시적으로 구현해야 합니다.
터키 접근성 규정과의 관계
2025년 6월 21일, 32933호 관보에 게재된 터키 대통령령 2025/10은 WCAG 표준을 인용해 공식적인 접근성 의무를 수립합니다. 이 대통령령은 해당 기관들이 국제적으로 인정된 지침에 부합하는 웹 접근성 조치를 구현하도록 요구하며, WCAG 1.4.13을 포함하는 Level AA 준수는 강력히 권장될 뿐 아니라, 가족·사회서비스부(Aile ve Sosyal Hizmetler Bakanlığı)가 발급하는 접근성 로고(Erişilebilirlik Logosu)를 취득하려는 기관에게는 요구사항입니다.
이 대통령령은 터키에서 운영되는 다양한 유형의 기관을 폭넓게 포괄합니다. 모든 행정 수준의 공공 기관과 정부 기관은 디지털 서비스를 접근 가능하게 만들어야 합니다. 민간 부문에서는, 전자상거래 플랫폼, 은행 및 금융 서비스 제공자, 병원 및 민간 의료 기관, 200,000명 이상의 가입자를 보유한 통신 사업자, 여행사, 민간 운송 회사, 그리고 교육부(Millî Eğitim Bakanlığı)의 인가를 받아 운영하는 민간 학교가 의무 대상에 포함됩니다.
WCAG 1.4.13은 e-Devlet 통합과 같은 전자정부 포털, 수수료나 금리 정보를 툴팁으로 표시하는 은행 및 핀테크 인터페이스, 호버 트리거 오버레이를 통해 추가 안내를 제공하는 의료 예약 시스템 등, 툴팁과 팝오버 패턴이 널리 사용되는 터키의 디지털 환경에서 특히 중요합니다. 1.4.13을 준수하지 못하는 은행 플랫폼은 툴팁으로 제공되는 이자 고지 내용을 저시력 고객이 읽지 못하게 만들 수 있으며, 이는 접근성뿐 아니라 금융 소비자 보호 측면에서도 문제를 야기합니다.
Erişilebilirlik Logosu를 추구하는 기관의 경우, 접근성 감사에는 자동화 도구로는 이러한 위반을 잡아낼 수 없기 때문에 호버 및 포커스 동작에 대한 수동 테스트가 포함됩니다. Accsible과 같은 접근성 오버레이 SDK를 사용하는 조직은, SDK 자체가 삽입하는 위젯 기반 툴팁, 가이드 투어 팝오버, 컨텍스트 도움말 패널이 1.4.13의 세 가지 요구사항 — Escape로 닫을 수 있고, 호버해도 사라지지 않으며, 사용자 행동 전까지 지속되는지 — 를 모두 완전히 준수하는지 반드시 확인해야 합니다. 그렇지 않으면, 접근성을 개선하기 위해 도입한 도구가 오히려 새로운 장벽을 만들어 규정 준수와 사용자 신뢰를 모두 훼손하게 됩니다.
