pointer-events を正しく理解すべき理由
モーダルのオーバーレイを表示したのに背後のボタンが押せない、サブメニューが opacity: 0 なのにホバーが反応してしまう——実務でよく遭遇するこうした不具合の多くは、pointer-events を「イベントを切るスイッチ」程度にしか理解していないことが原因です。
このプロパティの本質は次の一点です。
「ブラウザがヒットテスト(hit-testing)を行う際、その要素をイベントターゲットの候補から除外するかどうか」
ブラウザはポインターイベントを発火する前に、必ず最前面の要素から確認します。その要素が pointer-events: none であれば、ブラウザはその要素をスキップして、下にある次の要素を探します。つまりこれは「イベントを無効化する」のではなく、ターゲット選定そのものを変えるという挙動です。この視点さえ押さえれば、他の挙動はほぼすべて説明できます。
本記事は CSS-Tricks の pointer-events リファレンス をベースに、実務でハマりやすいポイントを中心に再構成したものです。

基本構文と11個の値
/* HTML + SVG 共通 */
pointer-events: auto; /* 初期値。通常通りイベントを受け取る(SVGでは visiblePainted と同義) */
pointer-events: none; /* この要素はイベントターゲットにならない。下の要素が受け取る */
/* SVG 専用の値 */
pointer-events: visiblePainted; /* 可視状態かつ fill / stroke 上のみ */
pointer-events: visibleFill; /* 可視状態かつ fill 上のみ(fill:none でも可) */
pointer-events: visibleStroke; /* 可視状態かつ stroke 上のみ */
pointer-events: visible; /* 可視状態かつ fill / stroke 上のみ */
pointer-events: painted; /* visibility 無関係、fill / stroke 上のみ */
pointer-events: fill; /* visibility 無関係、fill 上のみ */
pointer-events: stroke; /* visibility 無関係、stroke 上のみ */
pointer-events: bounding-box; /* 図形の形に関係なく、バウンディングボックス内すべて */
pointer-events: all; /* fill / stroke / visibility 無関係、すべて */
/* グローバル値 */
pointer-events: inherit;
pointer-events: initial;
pointer-events: revert;
pointer-events: unset;
実務で使う値は実質3つ
正直なところ、通常のWebアプリ開発で日常的に使うのは auto、none、たまに bounding-box 程度です。残りのSVG値は、アイコン・チャート・地図などのグラフィックインタラクションを実装する場面でしか登場しません。
最頻出パターン:モーダルオーバーレイのクリック透過
<div class="overlay">
<div class="modal">...</div>
</div>
/* 全画面を覆うコンテナはクリックを透過させる */
.overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none; /* ⚠️ 継承される! */
}
/* モーダル本体は再度クリックを受け取れるように戻す */
.modal {
pointer-events: auto;
}
ここで最も多いミスが、次で説明する「継承」の問題です。
![]()
必ず押さえたい4つの罠
1. pointer-events は継承される
pointer-events は inherited: yes のプロパティです。親に none を指定すると、子要素もすべて none になります。そのため、モーダルパターンでは .modal { pointer-events: auto; } で明示的に戻すコードが必須です。
.parent { pointer-events: none; }
.child { pointer-events: auto; } /* 必要な子だけ復活させる */
2. イベント伝播(バブリング)は依然として動作する
pointer-events: none の親の中に pointer-events: auto の子がある場合:
- 子が
event.targetになり - イベントは通常通り キャプチャ → ターゲット → バブル のフェーズを経由します
- 親に登録した
click、pointerenter、pointerleaveのリスナーも正常に実行されます
つまり pointer-events はターゲット選定だけを変え、イベント伝播には一切干渉しません。これを知らないと「親に none を指定したのに、なぜイベントが上がってくるのか」とハマることになります。
3. これは「無効化」ではない
- キーボードフォーカスは依然として可能です。
Tabでフォーカスが当たり、フォーム操作もできます。 - 本当に無効化したい場合の使い分け:
- フォームコントロール →
disabled属性 - 領域全体をポインター・キーボード・アクセシビリティツリーから除外 →
inert属性
- フォームコントロール →
4. テキスト選択は防げない
pointer-events: none でも Ctrl/Cmd + A によるテキスト選択は可能です。テキスト選択を防ぐには別途:
.no-select {
user-select: none;
}
実務頻出パターン:見えないサブメニュー
.menu-item .submenu {
opacity: 0;
pointer-events: none; /* opacity:0 だけだと見えなくてもクリックされる */
transition: opacity .2s ease;
}
.menu-item:hover .submenu {
opacity: 1;
pointer-events: auto;
}
opacity: 0 だけでは、見えていなくてもクリックできてしまいます。これはユーザー視点では「見えない要素がクリックを横取りする」という最悪のUXバグにつながります。必ず pointer-events も同時にトグルしてください。
SVG値はいつ使うのか
visiblePainted: SVGのデフォルト挙動。不可視・透明領域はイベントを無視。bounding-box: アイコンの空の角の領域までクリック範囲にしたいとき。all:fill:noneのアウトラインアイコンでも、全体でクリックを受けたいとき。
地図やチャートでSVGパスのヒット領域を細かく調整する際に有用です。

まとめと次のステップ
要点を整理すると:
| 目的 | 正しいツール |
|---|---|
| クリック透過 / イベントターゲットから除外 | pointer-events: none |
| フォームコントロールの無効化 | disabled |
| 領域全体の無効化(アクセシビリティ含む) | inert |
| テキスト選択の防止 | user-select: none |
日本の開発コミュニティ的文脈で一言。 Qiita でも繰り返し議論されているテーマですが、「モーダル背景のクリックを防ぐ」目的で z-index だけで解決しようとして、結果的にアクセシビリティを損なうケースが後を絶ちません。pointer-events: none + pointer-events: auto の組み合わせでオーバーレイを処理し、同時に背景コンテナへ inert を付与するのが、現行ブラウザ基準では最もクリーンな実装です。特に管理画面でキーボード操作のQAが入ると、pointer-events だけでは不十分であることがすぐに実感できるはずです。
限界と注意点。 pointer-events はあくまでポインターイベントのターゲット選定のみを扱います。スクリーンリーダー、キーボードナビゲーション、フォーカストラップといったアクセシビリティ課題は一切解決しません。またSVG値はブラウザごとに微妙な差異がある可能性があるため、プロダクション投入前に Safari / Firefox での動作確認を必ず行ってください。
次の学習ステップ。 このプロパティを理解したら、自然に次に繋がるテーマは (1) inert 属性とフォーカストラップ、(2) イベントデリゲーションと event.target / event.currentTarget の違い、(3) アクセシビリティツリーと aria-hidden の関係です。特に (3) はモーダル実装時に pointer-events とセットで登場します。