본문으로 건너뛰기
wooncloud

disabled 버튼에 툴팁이 안 뜨는 이유와 해결

disabled 버튼은 포인터 이벤트를 차단해 툴팁 표시를 방해하므로 CSS의 pointer-events: none을 활용해 이벤트를 부모 요소로 전달하는 해결법과 함께 키보드 사용자를 위한 접근성 보완책을 제시합니다

·6 min read· views·

disabled 버튼에 툴팁이 안 뜨는 이유와 해결

비활성(disabled) 버튼에 마우스를 올리면 "왜 못 누르는지"를 툴팁으로 알려주고 싶을 때가 있다. 권한이 없거나, 지금 위치에서는 불가능한 액션이라거나. 그런데 커스텀 툴팁을 달아보면 disabled 버튼 위에서는 툴팁이 아예 안 뜬다. 버튼을 <span onMouseEnter> 같은 트리거로 감싸도 마찬가지다.

원인은 브라우저의 이벤트 모델에 있고, 해결은 CSS 한 줄이다.

왜 안 뜨는가

HTML 명세상 disabled 상태의 폼 컨트롤은 포인터 이벤트를 발생시키지 않는다. disabled 버튼 위에 마우스를 올려도 mouseenter, mouseover, mousemove, click 이 전혀 디스패치되지 않는다.

문제는 여기서 끝나지 않는다. 툴팁 트리거를 버튼의 부모 요소에 둬도 안 된다. disabled 버튼이 포인터의 타깃을 "삼켜버려서", 그 위에 마우스가 있는 동안에는 감싸는 부모의 mouseenter 조차 불리지 않기 때문이다.

게다가 disabled 버튼은 포커스도 받지 못한다. 그래서 onFocus 로 툴팁을 띄우는 키보드 경로도 막힌다.

// ❌ 이렇게 감싸도 disabled 버튼 위에선 onMouseEnter 가 안 온다
<span onMouseEnter={showTip} onMouseLeave={hideTip}>
  <button disabled>새로 만들기</button>
</span>

해결: 버튼에 pointer-events: none

비활성 버튼에 pointer-events: none 을 준다. 그러면 포인터가 버튼을 그대로 "통과"해서, 버튼을 감싸는 부모 트리거가 hover 를 정상적으로 수신한다. 툴팁이 뜬다.

<span
  onMouseEnter={showTip}
  onMouseLeave={hideTip}
  style={{ display: "inline-flex" }}
>
  <button
    disabled
    style={{ pointerEvents: "none", cursor: "not-allowed" }}
  >
    새로 만들기
  </button>
  {tipOpen && <Bubble>공유받은 폴더 안에서만 만들 수 있어요</Bubble>}
</span>

핵심은 세 가지다.

  • 툴팁 트리거는 버튼을 감싸는 별도 요소여야 한다. 그 요소에 hover 핸들러를 둔다.
  • disabled 버튼에 pointer-events: none 을 줘서 포인터가 트리거까지 도달하게 한다.
  • cursor: not-allowed 는 트리거(부모)에 주는 편이 안전하다. pointer-events: none 인 버튼은 자기 cursor 를 적용하지 못하기 때문이다.

레이아웃 함정: full-width 버튼

버튼이 width: 100% 로 컨테이너를 꽉 채우는 디자인이라면, 감싸는 트리거도 같은 폭이어야 한다. 트리거를 무심코 display: inline-flex(콘텐츠 폭) 로 두면, 그 안의 width: 100% 버튼이 트리거 폭에 갇혀 찌그러진다.

트리거를 display: flex; width: 100% 로 블록처럼 펴주면 버튼이 원래 폭을 유지한다.

<span style={{ display: "flex", width: "100%" }}>
  <button disabled style={{ width: "100%", pointerEvents: "none" }}>…</button>
</span>

접근성: 툴팁만으로 끝내지 말 것

이 방법은 hover 전용이다. disabled 버튼은 포커스가 안 되므로 키보드·스크린리더 사용자는 툴팁 내용을 못 본다. 툴팁에만 이유를 담으면 그들에게는 "이유 없이 막힌 버튼"이 된다.

두 가지 보완책이 있다.

  • 같은 정보를 항상 보이는 텍스트로도 제공한다. 화면 상단 설명문이나 버튼 옆 안내 문구처럼, 마우스 없이도 읽히는 곳에 사유를 한 번 더 둔다.
  • 또는 진짜로 disable 하지 않는다. disabled 속성 대신 aria-disabled="true" 로 표시하고, 시각적으로만 비활성처럼 스타일링하며, onClick 에서 아무 일도 하지 않게(또는 안내를 띄우게) 막는 방식이다. 이러면 버튼이 여전히 포커스·hover 가능하므로 툴팁과 키보드 접근성을 모두 살릴 수 있다. 대신 "클릭은 되지만 아무 일도 안 일어남"을 직접 처리해야 한다.

어느 쪽이든, 툴팁은 보조 수단이지 유일한 안내 채널이 되어선 안 된다는 원칙은 같다.

정리

  • disabled 폼 컨트롤은 포인터 이벤트를 삼켜서, 자신은 물론 감싸는 부모의 hover 도 막는다.
  • 버튼에 pointer-events: none 을 주면 포인터가 통과해 부모 트리거가 hover 를 받아 툴팁이 뜬다.
  • full-width 버튼이면 트리거도 width: 100% 로 펴준다.
  • disabled 는 포커스 불가 → 툴팁만으로 안내하지 말고, 항상 보이는 텍스트나 aria-disabled 대안으로 접근성을 챙긴다.