컴포넌트 패턴
툴팁
요소에 마우스를 올리거나 키보드 포커스가 갔을 때 짧은 설명을 띄우는 툴팁과, 버튼을 눌러 정보를 여는 토글팁을 접근성 있게 만드는 방법입니다. WCAG 1.4.13의 닫기·호버·유지 조건을 함께 다룹니다.
언제 쓰나요
툴팁(tooltip)은 어떤 요소에 마우스 포인터를 올리거나 키보드 포커스가 들어갔을 때 나타나, 그 요소를 짧게 설명하는 작은 팝업입니다. 툴팁은 포커스를 받지 않으며, 트리거 요소에서 포커스나 포인터가 떠나면 사라집니다.
- 적합한 경우: 아이콘 버튼의 보충 설명, 단축키 안내(예: “복사 (Ctrl+C)”)처럼 몰라도 작업을 할 수 있는 짧은 텍스트.
- 맞지 않는 경우: 링크·버튼처럼 조작할 수 있는 내용이 들어가야 하거나, 내용이 길거나, 꼭 읽어야 하는 정보(입력 형식, 오류 등). 이런 내용은 화면에 항상 보이게 두거나 디스클로저, 대화상자로 만듭니다.
- 터치 화면에는 호버가 없고, 포커스가 들어갈 때만 뜨는 툴팁은 모바일 사용자가 보기 어렵습니다. 중요한 설명일수록 툴팁에만 넣지 않습니다.
title 속성은 툴팁 대안이 아닙니다
HTML title 속성이 만드는 브라우저 기본 툴팁은 키보드 포커스로는 나타나지 않고, 터치 화면에서는 볼 수 없으며, 글자 크기를 키울 수 없고, 포인터를 올린 채 기다려야 합니다. 화면낭독기도 설정에 따라 읽거나 읽지 않습니다. 따라서 중요한 정보를 title에만 넣지 않습니다.
툴팁과 토글팁
“i”나 “?” 아이콘을 눌러서 설명을 여는 것은 툴팁이 아니라 토글팁(toggletip)입니다. 둘은 동작과 마크업이 다릅니다.
| 구분 | 툴팁 | 토글팁 |
|---|---|---|
| 여는 방법 | 호버 또는 포커스 (자동) | 버튼 클릭·Enter·Space |
| 트리거 | 원래 기능이 있는 요소(버튼, 링크, 입력란 등) | 설명을 여는 것만 하는 전용 버튼 |
| 연결 방법 | tooltip + 트리거의 aria-describedby | 버튼 옆 라이브 리전(status)에 내용을 넣어 읽음 |
| 닫는 방법 | Esc, 포커스·포인터 이탈 | Esc, 바깥 클릭, 포커스 이탈 |
| 터치 화면 | 사실상 보기 어려움 | 탭으로 열 수 있음 |
토글팁은 ARIA 명세나 APG에 정해진 패턴이 아니라, 여러 디자인 시스템에서 널리 쓰는 구현 방식입니다. 라이브 리전 대신 디스클로저(aria-expanded)로 만들어도 됩니다.
구조
- 툴팁 요소에
tooltip역할과id를 줍니다. - 툴팁을 띄우는 트리거 요소에
aria-describedby로 툴팁id를 연결합니다. 툴팁 내용이 트리거의 설명이 됩니다. - 아이콘만 있는 버튼처럼 툴팁 내용이 곧 요소의 이름이라면
aria-labelledby로 연결해 이름으로 쓸 수도 있습니다. 이때 같은 텍스트를 이름과 설명에 중복으로 연결하지 않습니다. - 툴팁은 포커스를 받지 않습니다. 툴팁 안에 링크·버튼 같은 조작 가능한 요소를 넣지 않습니다.
aria-describedby는 참조 대상이hidden이어도 설명을 계산하므로, 툴팁이 숨겨진 상태에서도 트리거에 포커스하면 설명이 읽힙니다.
<span class="tooltip-wrap">
<button type="button" aria-describedby="tip-copy">링크 복사</button>
<div role="tooltip" id="tip-copy" hidden>현재 주소를 클립보드에 복사합니다 (Ctrl+C)</div>
</span>role="tooltip" 지원 현황
화면낭독기는 role="tooltip" 자체보다 aria-describedby 연결을 통해 툴팁 내용을 읽는 경우가 대부분입니다. 그래서 역할만 붙이고 연결을 빠뜨리면 툴팁 내용이 전달되지 않습니다. APG도 이 패턴을 아직 작업 중(work in progress)으로 표시하고 있습니다.
키보드 동작
| 키 | 동작 |
|---|---|
| Tab | 트리거에 포커스가 들어가면 툴팁이 나타납니다. 포커스가 떠나면 사라집니다. 툴팁 자체는 Tab 순서에 들어가지 않습니다. |
| Esc | 열린 툴팁을 닫습니다. 포커스는 트리거에 그대로 둡니다. 포인터 호버로 열린 경우에도, 포커스가 다른 곳에 있더라도 닫혀야 합니다. |
토글팁은 일반 버튼처럼 Enter·Space로 열고, Esc로 닫습니다.
필요한 ARIA
| 역할·속성 | 붙이는 곳 | 설명 |
|---|---|---|
tooltip | 툴팁 요소 | 요소에 대한 설명을 보여 주는 팝업임을 알립니다. |
aria-describedby | 트리거 요소 | 툴팁 id를 가리켜 툴팁 내용을 설명으로 연결합니다. 툴팁 패턴의 핵심입니다. |
aria-labelledby | 트리거 요소 (선택) | 툴팁이 아이콘 버튼의 이름 역할을 할 때 aria-describedby 대신 씁니다. |
status (aria-live="polite") | 토글팁 내용 영역 | 버튼을 누를 때 넣은 내용을 화면낭독기가 읽게 합니다. |
aria-label | 토글팁 버튼 | “i” 아이콘처럼 글자가 없는 버튼에 “배송비 정보” 같은 이름을 줍니다. |
직접 해 보기
세 가지 구현을 비교합니다. 마우스를 올려 보고, Tab으로 포커스해 보고, 툴팁이 열린 상태에서 Esc를 눌러 보세요. 올바른 구현에서는 포인터를 툴팁 위로 옮겨도 툴팁이 사라지지 않습니다.
잘못된 구현: title만 사용
키보드로 포커스해도 설명이 보이지 않고, 포인터를 올린 채 기다려야만 나타납니다.
올바른 구현: 툴팁
현재 주소를 클립보드에 복사합니다 (Ctrl+C)토글팁
배송비
버튼을 누르면 설명이 나타나고 화면낭독기가 읽습니다. Esc나 바깥 클릭으로 닫습니다.
실시간 코드
전체 코드
<button type="button" title="현재 주소를 클립보드에 복사합니다">
링크 복사
</button><span class="tooltip-wrap" id="wrap">
<button type="button" id="trigger" aria-describedby="tip">링크 복사</button>
<span role="tooltip" id="tip" hidden>현재 주소를 클립보드에 복사합니다</span>
</span>var wrap = document.getElementById('wrap');
var trigger = document.getElementById('trigger');
var tip = document.getElementById('tip');
var hovering = false, focused = false, dismissed = false, timer;
function show() { if (!dismissed) { clearTimeout(timer); tip.hidden = false; } }
function hideSoon() {
clearTimeout(timer);
timer = setTimeout(function () {
if (!hovering && !focused) tip.hidden = true;
}, 300);
}
// 감싼 요소에 호버를 걸면 포인터를 툴팁 위로 옮겨도 유지됩니다(호버 가능).
wrap.addEventListener('mouseenter', function () { hovering = true; show(); });
wrap.addEventListener('mouseleave', function () {
hovering = false;
if (!focused) dismissed = false;
hideSoon();
});
trigger.addEventListener('focus', function () { focused = true; show(); });
trigger.addEventListener('blur', function () {
focused = false;
if (!hovering) dismissed = false;
hideSoon();
});
// Esc: 포인터·포커스를 옮기지 않고 닫을 수 있습니다(닫기 가능).
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !tip.hidden) {
dismissed = true;
tip.hidden = true;
}
});<span class="toggletip">
<button type="button" id="tt-btn" aria-label="배송비 정보">i</button>
<span role="status" id="tt-out"></span>
</span>var btn = document.getElementById('tt-btn');
var out = document.getElementById('tt-out');
btn.addEventListener('click', function () {
out.textContent = ''; // 다시 눌러도 읽히도록 비운 뒤
setTimeout(function () {
out.textContent = '3만 원 이상 주문하면 배송비가 무료입니다.';
}, 100);
});
btn.addEventListener('keydown', function (e) {
if (e.key === 'Escape') out.textContent = '';
});
btn.addEventListener('blur', function () { out.textContent = ''; });화면낭독기에서는
| 상황 | 들리는 안내 (예) |
|---|---|
| 툴팁이 연결된 버튼에 포커스 | 링크 복사, 버튼, 현재 주소를 클립보드에 복사합니다 (Ctrl+C) |
| title만 있는 버튼에 포커스 | 링크 복사, 버튼(설명은 화면낭독기 설정에 따라 읽히지 않을 수 있음) |
| 토글팁 버튼에 포커스 | 배송비 정보, 버튼 |
| 토글팁 버튼을 누름 | 3만 원 이상 주문하면 배송비가 무료입니다. |
점검 체크리스트
- 툴팁이 마우스 호버뿐 아니라 키보드 포커스에서도 나타납니다.
- Esc로 포인터나 포커스를 옮기지 않고 툴팁을 닫을 수 있습니다(닫기 가능).
- 포인터를 트리거에서 툴팁 위로 옮겨도 툴팁이 사라지지 않습니다(호버 가능).
- 사용자가 닫거나 포커스·포인터가 떠나기 전에는 시간이 지났다고 사라지지 않습니다(유지).
- 트리거에
aria-describedby(또는 이름이면aria-labelledby)로 툴팁이 연결되어 있습니다. - 툴팁 안에 링크·버튼 같은 조작 가능한 요소가 없습니다.
- 중요한 정보를
title속성이나 툴팁에만 넣지 않았습니다. - “i”·“?” 아이콘을 눌러 여는 설명은 토글팁(버튼 + 라이브 리전) 또는 디스클로저로 만들었고, 아이콘 버튼에 이름이 있습니다.
- 툴팁 글자와 배경의 명도 대비가 충분하고, 툴팁이 다른 중요한 내용을 가리지 않습니다.
관련 WCAG 성공 기준
- 1.4.13 호버 또는 포커스 시 콘텐츠 (AA): 추가로 나타나는 내용은 닫을 수 있고(dismissible), 그 위로 포인터를 옮길 수 있고(hoverable), 사용자가 치우기 전까지 유지되어야(persistent) 합니다.
- 1.3.1 정보와 관계 (A): 툴팁과 트리거의 관계를
aria-describedby로 전달합니다. - 2.1.1 키보드 (A): 키보드만으로 툴팁 내용을 확인할 수 있습니다.
- 1.4.3 명도 대비(최소) (AA)
- 4.1.2 이름, 역할, 값 (A): 아이콘 버튼과 토글팁 버튼에 이름이 있습니다.