albert.walickihire me
← all solutions
css

How to change the cursor on hover in CSS?

Change the mouse cursor with the CSS cursor property: which value to use for clickable, draggable or disabled elements, and how to use a custom image.

Set the cursor property on the element. You don't need :hover for it, because the cursor only shows while the pointer is over the element anyway.

.card {
  cursor: pointer;
}

Links with an href get the pointing hand from the browser already. Add cursor: pointer to other things people click, such as a clickable card, a <summary>, a custom toggle or the <label> of a checkbox. Buttons show the normal arrow in most browsers. Many design systems give them cursor: pointer too, and either choice is fine as long as the whole site does the same thing.

An <a> without href doesn't get the hand, because to the browser it isn't a link.

Cursor values worth knowing

  • auto: the initial value. The browser decides, for example a text cursor over text and an arrow elsewhere.
  • default: the normal arrow, whatever is underneath.
  • pointer: the hand, for clickable elements.
  • text: the I-beam, for text that can be selected or edited.
  • move: something can be moved in any direction.
  • grab and grabbing: a draggable item, before and while it's dragged.
  • not-allowed: the action isn't available, like a disabled button.
  • wait: the interface is busy and can't be used right now.
  • progress: something loads in the background, but the user can keep working.
  • help: more information is available, like an <abbr> with a title.
  • zoom-in and zoom-out: an image that opens bigger or closes again.
  • col-resize and row-resize: a divider that resizes columns or panels.
  • crosshair: precise picking, like a color picker or a drawing canvas.
  • none: hides the cursor, for example in a game that draws its own.

A few of them in practice:

.slider-handle {
  cursor: grab;
}

.slider-handle:active {
  cursor: grabbing;
}

.button:disabled {
  cursor: not-allowed;
}

Touch screens have no cursor, so don't make it the only signal. A disabled button still needs to look disabled, and a loading state still needs a spinner or text.

Custom cursor image

.canvas {
  cursor: url('/cursors/pen.svg') 4 28, crosshair;
}

The two numbers are the hotspot: the point of the image that actually clicks, measured in pixels from its top-left corner. Here it's the tip of a pen drawn in the bottom-left of a 32×32 image. They're optional, and without them the top-left corner is used.

The keyword at the end is required. It's the fallback when the image can't be loaded, and without it the whole declaration is invalid and ignored. You can list several images before it, and the browser uses the first one it can load.

Keep the image small, around 32×32 pixels. Browsers have a size limit and may ignore bigger images. I'd also keep custom cursors for specific tools like drawing or cropping, not the whole page: people are used to their system cursor, and some rely on a larger or high-contrast one.

Gotcha: pointer-events: none hides the cursor too

/* The cursor never shows */
.link.is-disabled {
  pointer-events: none;
  cursor: not-allowed;
}

With pointer-events: none, the element ignores the mouse completely, so the cursor comes from whatever is underneath it. Put the cursor on a wrapper instead:

<span class="link-wrapper">
  <a class="link is-disabled" href="/pricing">Pricing</a>
</span>
.link-wrapper {
  cursor: not-allowed;
}

.link.is-disabled {
  pointer-events: none;
}

Keep in mind that pointer-events: none doesn't stop keyboard users from focusing the link and pressing Enter. How to disable a link with CSS covers the proper way.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch