CSSで、要素に合わせてマウスカーソルの形を変える方法
マウスカーソルの形は、CSS の cursor プロパティで要素ごとに変えられます。cursor: pointer なら指を差した手、cursor: not-allowed なら丸に斜線の形になります。自分で用意した画像も使えますが、そのときは url() の後ろに、画像が使えないときに代わりに使うキーワードを必ず書きます。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。カーソルを実際に画面に出して確かめるため、Chromium をヘッドレスではなく Xvfb の仮想の画面に開き、xdotool で X のマウスポインターを動かしました。表示されたカーソルの画像は X サーバーから取り出し、カーソルテーマ(Adwaita、32px)のどの画像と同じかを調べています。記事の画像に写っているカーソルは、このカーソルテーマの絵柄です。Firefox と Safari、Windows と macOS、タッチ操作の端末は試していません。
cursor にキーワードを書く
書き方は、要素に cursor とキーワードを指定するだけです。
.card {
cursor: pointer;
}
検証環境で、キーワードを1つずつ指定したタイルにマウスを重ね、出たカーソルを撮りました。

MDN の cursor のページには、キーワードごとの意味が書かれています。よく使うものを抜き出すと、次のとおりです。
| キーワード | MDN の説明 |
|---|---|
pointer |
リンクを示す。ふつうは指差す手の画像 |
not-allowed |
要求された操作が受け付けられない |
grab / grabbing |
何かをつかんで動かせる/動かしている |
wait |
プログラムがビジー状態で、ユーザーは操作できない |
progress |
バックグラウンドでビジー状態だが、ユーザーは操作できる |
help |
ヘルプが使える |
none |
カーソルを表示しない |
wait と progress は、どちらも「処理中」を表しますが、ユーザーが操作できるかどうかが違います。上の画像でも、wait は円の形だけ、progress は矢印に小さな円が付いた形でした。
move を指定したタイルでは、上下左右に矢印の付いた画像が出ました。この画像は、カーソルテーマ Adwaita の中で all-scroll という名前の画像と同じものでした。
CSS を書かないときに出るカーソル
cursor の初期値は auto で、ブラウザが場所に合わせてカーソルを選びます。CSS で何も指定していない要素にマウスを重ねると、次のようになりました。

| 要素 | getComputedStyle() の cursor |
出たカーソル |
|---|---|---|
<a href> |
pointer |
指を差した手 |
<a>(href なし) |
auto |
文字の上では I の字 |
<button> |
default |
矢印 |
<button disabled> |
default |
矢印 |
<input type="text"> |
text |
I の字 |
<p> の文字 |
auto |
I の字 |
指の形になったのは、href のあるリンクだけでした。<button> は矢印のままです。Chromium のソースにある既定のスタイルシート(html.css)には、href のある <a> に cursor: pointer、<input> に cursor: text が書かれています。ボタンでも指の形にしたいときは、ボタンに cursor: pointer を書きます。
子の要素にも引き継がれる
CSS の仕様(CSS Basic User Interface Module Level 4)では、cursor は継承されるプロパティです。親の要素に書けば、中の要素にも同じカーソルが出ます。
.card {
cursor: pointer;
}
<div class="card">
<p>カードの文章</p>
<input type="text" value="カードの中の入力欄">
</div>
カードの中の文章にマウスを重ねると、<p> の cursor は pointer になり、指の形が出ました。一方、入力欄の cursor は text のままで、I の字が出ました。前の節で見たとおり、Chromium の既定のスタイルシートでは <input> に cursor: text が書かれているので、親の pointer は引き継がれませんでした。
無効なボタンに not-allowed を出す
押せないボタンで丸に斜線の形を出したいときは、:disabled に cursor: not-allowed を書きます。
button:disabled {
cursor: not-allowed;
}
disabled を付けたボタンにマウスを重ねると、not-allowed のカーソルが出ました。
ただし、同じボタンに pointer-events: none も付けると、ボタンの cursor は not-allowed なのに、出たカーソルは指の形でした。検証では、cursor: pointer を指定した要素でボタンを囲んでいました。CSS の仕様では、要素が重なっているとき、カーソルを決めるのは、その位置をクリックしたときにクリックを受け取る要素です。MDN の pointer-events のページにあるとおり、pointer-events: none の要素はポインターイベントの対象にならないので、その下の親の要素のカーソルが出ます。
/* この組み合わせでは、not-allowed は出なかった */
button:disabled {
cursor: not-allowed;
pointer-events: none;
}
ドラッグしている間だけ形を変える
ドラッグで動かせる要素には grab を書き、押している間だけ :active で grabbing に変えます。
.card {
cursor: grab;
}
.card:active {
cursor: grabbing;
}
カードを動かす処理は JavaScript で書きました。カードの上にマウスを重ね、マウスのボタンを押したままカードを動かす操作を2回行ったところを録画しました。

押す前は grab の開いた手、押した直後と、押したまま動かしている間は grabbing の握った手でした。動かしている間も、カードは :active に当てはまっていました。ボタンを離すと、開いた手に戻りました。
自分で用意した画像をカーソルにする
画像を使うときは url() で画像を指定し、最後にキーワードを書きます。
.canvas {
cursor: url(ring.svg) 16 16, crosshair;
}
検証では、32×32px の SVG で、ピンクの輪の中心に点がある画像を作りました。
最後のキーワードを省くと、宣言ごと無視される
MDN の cursor のページにあるとおり、url() を並べた最後には、キーワードを必ず書きます。キーワードを省いて cursor: url(ring.svg); と書くと、CSS.supports() は false を返し、cursor は初期値の auto のままでした。画像は出ず、矢印のカーソルが出ました。画像が出ないときは、まずキーワードを書き忘れていないかを見てください。
url() の後の2つの数値は、画像の中で指す点
url() の後ろの2つの数値は、ホットスポットの座標です。ホットスポットは、画像の中で、実際にポインターが指している点です。画像の左上を 0 0 として、ピクセル単位で書きます。
同じ画像で、ホットスポットを 16 16 にしたときと、書かなかったときを比べました。灰色の十字は、ポインターがある位置です。

16 16 を書くと、輪の中心がポインターの位置に重なりました。書かないと、X サーバーに渡されたカーソルのホットスポットは 0,0 で、画像の左上がポインターの位置になりました。CSS の仕様では、座標を省いたときは画像ファイルに入っているホットスポットを使い、それもなければ画像の左上を使います。先端が画像の左上の角にある画像なら左上のままでもかまいませんが、輪や十字のように中心で指す画像では、中心の座標を書きます。
大きすぎる画像は使われない
MDN の cursor のページによると、Firefox と Chromium では、カーソルの画像は既定で 128×128px までに制限されています。MDN は、32×32px までにすることを勧めています。
同じ絵を 128×128px と 200×200px の SVG にして試しました。
| 画像の大きさ | 出たカーソル |
|---|---|
| 128×128px | 指定した画像(128×128px) |
| 200×200px | 画像は使われず、最後のキーワードの crosshair |
200×200px のときも、getComputedStyle() の cursor には指定どおりの url() が入っていました。CSS としては正しく読み込まれていても、画像が大きすぎると画面に出るのは代わりのキーワードのカーソルです。





