Front-end developers and designers writing CSS
How to change the cursor in CSS, with examples
One property controls the mouse cursor in CSS. This guide goes from a single keyword to a custom image with a hotspot and fallbacks, then covers the cases that trip people up.
Step 1: Set a keyword on the element
.card {
cursor: pointer;
}
That is the whole job for most cases. A few things to know straight away:
- You do not need
:hover. The cursor only ever shows while the pointer is over the element, so.card:hover { cursor: pointer; }does nothing extra. cursoris inherited. Everything inside.cardgets the hand too, including its text. Reset children that should behave differently, for example.card p { cursor: auto; }.- The initial value is
auto. The browser then picks the I-beam over text, the hand over links and the arrow elsewhere.
Step 2: Pick the right keyword
There are 36 keywords. These cover most interfaces:
| You want to say | Keyword |
|---|---|
| This is clickable | pointer |
| This is disabled | not-allowed |
| You can drag this | grab, then grabbing while dragging |
| This window or object moves | move |
| Drag this border to resize a column | col-resize |
| Drag this edge or corner | ew-resize, ns-resize, nwse-resize, nesw-resize |
| Click to enlarge | zoom-in |
| Aim precisely | crosshair |
| Hover for an explanation | help |
| Working, but you can carry on | progress |
| Busy, please wait | wait |
The CSS cursor reference has a page for every keyword with a live stage, so you can see what your own system draws.
Step 3: Use your own image with url()
.brand-zone {
cursor: url("cursor.png") 4 2, auto;
}
- The keyword at the end is required. Without it the whole declaration is invalid and ignored.
- The two numbers are the hotspot: the pixel, counted from the image’s top-left corner, that does the clicking. For an arrow it is the tip. Leave them out and the browser uses the hotspot stored in a
.curfile, or0 0. - Keep the image at 32x32 pixels. Anything above 128x128 is ignored, and larger cursors get swapped for the fallback near the edges of the page. The size limits guide has the details.
- SVG works in current browsers and stays sharp on high-density screens. Give the root
svgelementwidthandheightattributes.
The cursor maker exports a PNG and this CSS line together, with the hotspot already filled in.
Step 4: Add fallbacks in a sensible order
.brand-zone {
cursor:
url("cursor.svg") 4 2,
url("cursor.png") 4 2,
auto;
}
The browser tries each image in order and uses the first one that loads and fits the rules. The keyword is the last resort. Pick a keyword that means the same thing as your image: pointer behind a custom hand, auto or default behind a custom arrow.
Step 5: Handle states and drags
button:disabled,
[aria-disabled="true"] {
cursor: not-allowed;
}
.card {
cursor: grab;
}
.card:active {
cursor: grabbing;
}
/* While a drag is running, keep the closed hand even off the card */
html.is-dragging,
html.is-dragging * {
cursor: grabbing;
}
Add is-dragging to the html element on pointerdown and remove it on pointerup and pointercancel. Without it, the cursor flips back the moment the pointer runs ahead of the element.
One exception: during native HTML drag and drop (the draggable attribute and dragstart), the operating system draws the drag cursor and ignores your CSS. Set event.dataTransfer.dropEffect to copy, move, link or none in dragover instead.
Step 6: Change the cursor for the whole page
Put the rule on the root element, not on body:
html {
cursor: url("cursor.png") 4 2, auto;
}
When the pointer is below a short page’s content, there is no body underneath it, and the browser uses the root element’s cursor. Remember that links, buttons and fields set their own cursors, so they will not show your image unless you target them too.
Step 7: Change the cursor from JavaScript
Toggle a class rather than writing styles directly. It keeps the cursors in your CSS and is easy to undo:
document.documentElement.classList.add('is-busy');
try {
await saveDraft();
} finally {
document.documentElement.classList.remove('is-busy');
}
html.is-busy,
html.is-busy * {
cursor: progress;
}
The finally block matters: without it, an error leaves the busy cursor on screen for good.
Step 8: Check why a cursor is not showing
- Is the rule crossed out in developer tools? A
url()without a closing keyword is invalid. - Is another rule winning? A more specific selector, or a framework’s reset, may set the cursor on the same element.
- Does the element have
pointer-events: none? Then the pointer goes straight through it and you see the cursor of whatever is underneath. - Is the image too big, or near an edge? Above 128x128 it is ignored. Near the page edges, browsers swap larger cursors (and, in Chrome 156 and Safari, any cursor) for the fallback.
- Is the pointer over a scrollbar or a native control? Browsers may keep their own cursor there.
- Is it inside an iframe? The page inside the frame decides the cursor there, not yours.
FAQ
Do buttons need cursor: pointer?
Browsers give links the hand and buttons the arrow. Most websites add pointer to buttons so everything clickable looks the same, and that is fine. Just remove it from disabled buttons.
How do I hide the cursor with CSS?
cursor: none. Use it only where something else shows the pointer’s position, keep it out of form fields, and scope it to a section. The hide-native-cursor pattern does all three.
Can a CSS cursor be animated?
No. Browsers show a single frame of an animated GIF and do not accept .ani files. For an animated cursor on a website, use a JavaScript follower such as the dot and ring follower.