---
title: "disabled 버튼에 툴팁이 안 뜨는 이유와 해결"
description: "disabled 버튼은 포인터 이벤트를 차단해 툴팁 표시를 방해하므로 CSS의 pointer-events: none을 활용해 이벤트를 부모 요소로 전달하는 해결법과 함께 키보드 사용자를 위한 접근성 보완책을 제시합니다"
date: 2026-07-23
updated: 2026-07-23T07:04:34.646Z
tags: [frontend, css, react, ux, accessibility]
canonical: https://blog.wooncloud.com/posts/disabled-button-tooltip-pointer-events
---

![disabled 버튼에 툴팁이 안 뜨는 이유와 해결](/images/posts/disabled-button-tooltip-pointer-events/863e6f8a-fc27-4bae-9363-b1be025155ff.webp)

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

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

## 왜 안 뜨는가

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

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

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

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

## 해결: 버튼에 pointer-events: none

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

```tsx
<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%` 로 블록처럼 펴주면 버튼이 원래 폭을 유지한다.

```tsx
<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` 대안으로 접근성을 챙긴다.
