CSS cursor: a complete guide to types, custom cursors, and best practices

- Andrés Cruz - ES En español

CSS cursor: a complete guide to types, custom cursors, and best practices

Changing the cursor with CSS is one of those small things that, when you discover them, you wonder how you weren't using them before.

It happened to me the exact same way: I wanted to improve the experience on a rather minimalist interface and started testing different cursors. That's when I learned two important lessons:

  1. Not all browsers load the same cursors,
  2. A poorly chosen cursor can ruin the user experience.

In this guide, I'm going to teach you everything I've learned about using cursors with CSS: how they work, which ones exist, how to create your own, which errors to avoid, and how to apply them without compromising the usability of your interface.

What the cursor property is and what it's for

The CSS cursor property defines the appearance of the mouse pointer when the user hovers over an element. It is conceptually simple: you change the value and the cursor changes its look visually.

When you hover over a text type input, the famous editing I-beam appears. Over an <a> link, the hand icon appears. All of this is decided by the browser depending on the element type... but you can override it at will with this property.

How it works and when it is applied

The browser assigns a default cursor to each element based on its semantic role, but you can change it on any CSS selector:

button {
 cursor: pointer;
}

Be very careful here: changing the cursor purely for aesthetics can confuse the user. I did it at first and ended up backtracking because users couldn't tell which elements were clickable and which were not. The cursor is an indicator of affordance —meaning it tells the user what they can do— and it should never lie to them.

Common errors when using cursors (and how to avoid them)

  • Using cursors that do not represent real actions (for example, setting cursor: pointer on non-clickable text)
  • Setting overly large custom cursors (it feels slow and clumsy when moving the mouse)
  • Not including a fallback when the browser does not support the cursor image
  • Applying cursor styles on touchscreens (where the cursor simply doesn't exist)

With the CSS cursor property, we can change the appearance of the pointer over certain elements on our website. Normally, when we visit a site, the cursor takes the shape of an arrow; but this changes when placed over certain elements —for example, a text type input— where it changes to show the text-editing cursor. We can control that behavior.

Cursors on the web with CSS

The use of custom cursors in a web application can significantly improve the end-user experience. In this article, we will see how to use both the predefined cursors provided by CSS and custom cursors created with your own images.

To use predefined cursors, it is enough to assign the corresponding value to the cursor property of the selector you want. For a custom cursor, on the other hand, you must provide the URL of an image that will visually represent the pointer.

Basic syntax of the cursor property in CSS

With CSS, you can set the cursor type using the following syntax:

/* where 'cursor' is the property and 'value' is the cursor type */
cursor: value;

Basic example of usage

Like everything in CSS, there is a property and a value. The following example turns the cursor over an .editable-text element into an I-beam cursor, which indicates to the user that they can write there:

.texto-editable {
 cursor: text;
}

Applying the cursor to specific elements

You can change the cursor on specific HTML elements without globally affecting the entire page. For example, here we define a pointer cursor for clickable cards and an ew-resize cursor for a resizable border:

.card {
 cursor: pointer;
}
.borde {
 cursor: ew-resize;
}

I like to test each cursor in several browsers because some, surprisingly, do not render certain values. It has happened to me especially with context-menu, which in some environments is simply ignored and falls back to the default cursor.

Complete list of cursors in CSS (with description and utility)

Below are the predefined cursors provided by the operating system or browser. Some of them may not load in all environments, depending on the operating system or browser used by your visitor; keep that in mind when choosing one for your interface:

  • default — Standard system arrow
  • pointer — Hand indicating that the element is clickable
  • text — I-beam for text selection or editing
  • wait — Indicates that the system is processing and the user must wait
  • progress — Loading in the background (the user can continue interacting)
  • help — Question mark or help available
  • crosshair — Precision cross, useful in graphic tools or maps
  • move — Free movement in all directions

Cursors for resizing elements

This group of cursors is especially useful when building interfaces with resizable panels, splitters, or adjustable grids:

  • e-resize — Resize to the right (east)
  • w-resize — Resize to the left (west)
  • n-resize — Resize upward (north)
  • s-resize — Resize downward (south)
  • ne-resize — Diagonal top-right
  • nw-resize — Diagonal top-left
  • se-resize — Diagonal bottom-right
  • sw-resize — Diagonal bottom-left
  • ns-resize — Vertical resizing (north-south)
  • ew-resize — Horizontal resizing (east-west)
  • col-resize — Column adjustment
  • row-resize — Row adjustment

Functional and warning cursors

These cursors communicate restrictions or special actions to the user, and are very useful in interfaces with drag-and-drop elements or blocked zones:

  • not-allowed — Indicates that the action is blocked or not permitted
  • no-drop — The element cannot be dropped in this area
  • copy — Indicates that an element will be copied
  • alias — Indicates an alias or alternative shortcut
  • none — Completely hides the cursor (use with caution)
  • grab — The element can be dragged
  • grabbing — The element is currently being dragged

Here is the full block of predefined cursors, ready to copy and use as a reference in your project:

/* predefined CSS cursors */
.alias           { cursor: alias; }
.auto            { cursor: auto; }
.cell            { cursor: cell; }
.copy            { cursor: copy; }
.context-menu    { cursor: context-menu; }
.crosshair       { cursor: crosshair; }
.default         { cursor: default; }
.help            { cursor: help; }
.move            { cursor: move; }
.none            { cursor: none; }
.pointer         { cursor: pointer; }
.progress        { cursor: progress; }
.text            { cursor: text; }
.vertical-text   { cursor: vertical-text; }
.wait            { cursor: wait; }

.no-drop         { cursor: no-drop; }
.not-allowed     { cursor: not-allowed; }

.grab            { cursor: grab; }
.grabbing        { cursor: grabbing; }

.all-scroll      { cursor: all-scroll; }

.col-resize      { cursor: col-resize; }
.row-resize      { cursor: row-resize; }

.e-resize        { cursor: e-resize; }
.n-resize        { cursor: n-resize; }
.s-resize        { cursor: s-resize; }
.w-resize        { cursor: w-resize; }

.ns-resize       { cursor: ns-resize; }
.ew-resize       { cursor: ew-resize; }
.ne-resize       { cursor: ne-resize; }
.nw-resize       { cursor: nw-resize; }
.se-resize       { cursor: se-resize; }
.sw-resize       { cursor: sw-resize; }
.nesw-resize     { cursor: nesw-resize; }
.nwse-resize     { cursor: nwse-resize; }

You can see it in practice here:

Custom Cursors with CSS: how to use your own images

To define a completely custom cursor, simply specify the URL of the image that will represent it as the value of the cursor property. You can chain multiple URLs sequentially: CSS will try to load the first image; if it fails —because it doesn't exist, the format isn't compatible, or the size exceeds the browser limit— it will automatically move to the next one. At the end of the chain, you must always include a standard fallback cursor:

/* custom cursor with fallback */
.pointer-custom {
  cursor: url(pointer-custom.gif),
          url(pointer-custom.png),
          pointer;
}

Using url() to load your own cursor

  • You can use .png, .gif, .cur, or even .svg in modern browsers.
  • Some browsers ignore the first image if it does not meet certain dimensions or if the format is not supported. That's why I always chain several options:
.cursor-custom {
  cursor: url("cursor.cur"),
          url("cursor.png"),
          pointer;
}

Note the pointer at the end: that is the mandatory fallback. Without it, if no image loads, the browser may end up without a cursor or behave unpredictably.

Fallbacks: how to ensure your cursor always works

The fallback strategy for custom cursors follows this priority order:

  • First URL → ideal and most compatible format (e.g., .png)
  • Second URL → alternative for less common browsers (e.g., .cur)
  • Final value → standard CSS cursor (pointer, default, etc.)

This strategy has saved me more than once, especially when a .png didn't load properly in Safari or when an animated .gif wasn't supported by Firefox in an older version.

Recommended formats: PNG, SVG, and CUR

  • .cur: native Windows format, optimal for compatibility in legacy IE/Edge
  • .png: the best balance between compatibility and quality; works in most modern browsers
  • .svg: perfect resolution at any pixel density, but cursor support is not universal yet
  • .gif: allows animated cursors, although with variable browser support

Cursor anchor point (hotspot)

There is a detail that many people overlook: the hotspot or anchor point. It is the exact coordinate within the cursor image that represents the "tip" —the pixel that acts as the actual click point. You define it with two numbers after the URL, in pixels from the top-left corner of the image:

/* hotspot at x=0, y=0 (top-left corner) */
.custom-svg {
  cursor: url("cursor.svg") 0 0, pointer;
}

/* centered hotspot on a 32x32 image */
.crosshair-custom {
  cursor: url("crosshair.png") 16 16, crosshair;
}

If you don't define the hotspot correctly, the click may be registered at a point different from where the pointer visually appears, creating a frustrating experience for the user.

Sizes, optimization, and cursor quality

I learned this through trial and error: if the image is too large, the cursor movement feels clumsy and slow because the browser has to scale it in real time. The recommended dimensions are 24×24 or 32×32 pixels. Some browsers have a maximum limit of 128×128 px, but in practice, the smaller and lighter the image, the better performance you will get.

UX Best Practices when choosing a cursor

  • When changing the cursor truly improves the experience:
    • Elements that are not natively interactive but are interactive in your UI (clickable cards, divs with event listeners, etc.)
    • Drag and drop interfaces (cursor: grab before dragging, cursor: grabbing during drag)
    • Graphical elements such as <canvas>, custom sliders, or visual editors
    • Highly visual interfaces or games where the cursor is part of the visual language
  • Cases where you should NOT change the cursor:
    • If the element already has a standard behavior that the user recognizes: links take pointer, text takes text, and buttons take pointer. Respecting these conventions prevents confusion.
    • Do not use cursor: none unless you are building a complete custom cursor with JavaScript, because hiding the cursor without replacing it is a terrible experience.
  • Cursors and touchscreens (what nobody tells you):
    • On mobile and touch devices, there is no concept of a cursor, so do not rely on it as the sole indicator of interactivity.
    • Always complement with alternative visual cues: shadows, borders, color changes on :active state, icons, or feedback animations.

Accessibility and design: making cursors useful for everyone

The cursor you define in CSS does not affect all users equally, because not everyone interacts with your interface in the same way. Depending on the device:

  • Mouse: precise interaction, benefits from all cursor types
  • Trackpad: medium precision, cursors work but with less granularity
  • Touchscreen: no physical cursor; cursor styles have no effect
  • Stylus / digital pen: mixed behavior, varies depending on the OS and browser

Pointer media queries: pointer and any-pointer

CSS gives you a powerful tool to detect the user's pointer type and adapt your interface accordingly: the pointer and any-pointer media queries. This is especially useful for avoiding custom cursor logic where it makes no sense:

/* User is using a finger on a touchscreen → do not apply custom cursors */
@media (pointer: coarse) {
  .cursor-custom {
    cursor: auto;
  }
}

/* User has a precise pointer (mouse) → you can enrich the experience */
@media (pointer: fine) {
  .drag {
    cursor: grab;
  }
}

The coarse value identifies touch or low-precision devices, while fine identifies mice and trackpads. Using these queries allows you to build truly adaptive experiences.

Difference between cursor and caret-color

Many people confuse these two concepts, so it's worth clarifying:

  • The cursor property controls the mouse pointer, which follows the movement of the input device.
  • The caret-color property controls the color of the text cursor —that blinking vertical bar that appears inside an input or textarea when you are typing.
input {
  caret-color: red; /* changes the color of the typing insertion bar */
}

They are independent and complementary properties: cursor defines how the pointer looks when approaching the field, and caret-color defines how the insertion point looks once the user is typing inside it.

Ready-to-copy examples: CSS cursors for your project

Buttons, links, and inputs:

/* Any clickable button or link */
.boton {
  cursor: pointer;
}

/* Text input or editable area */
.editable {
  cursor: text;
}

Menus, sliders, and draggable elements:

/* Draggable element: grab before, grabbing during */
.drag {
  cursor: grab;
}
.drag:active {
  cursor: grabbing;
}

Blocked or unavailable zone:

/* Disabled button or action not allowed */
.bloqueado {
  cursor: not-allowed;
  opacity: 0.5;
}

Custom cursor with SVG and hotspot:

/* SVG as a cursor, with defined hotspot and standard fallback */
.custom-svg {
  cursor: url("cursor.svg") 0 0, pointer;
}

Frequently Asked Questions about CSS Cursors (FAQ)

  • Do cursors work on touchscreens?
    • No. On touch devices there is no physical cursor, so the cursor property has no visible effect.
  • Why doesn't my custom cursor appear?
    • The most common causes are: the image exceeds the maximum permitted size (usually 128 px), the format is not compatible with that browser, or the fallback value is missing at the end of the cursor declaration.
  • What image formats does CSS accept for custom cursors?
    • The most widely supported formats are .png, .gif, and .cur. The .svg format also works in modern browsers, but with partial support.
  • Can I put a different cursor on a div?
    • Yes, the cursor property is applied per CSS selector and works on any HTML element, not just native interactive elements.
  • What size should a custom cursor be?
    • The ideal size is 24×24 or 32×32 pixels. Larger sizes can cause the cursor movement to feel sluggish or lead the browser to reject it outright.
  • What is the hotspot and why does it matter?
    • The hotspot is the coordinate inside the cursor image that corresponds to the actual click point. It is defined with two pixel values after the URL: url("cursor.png") 0 0, pointer. If you don't define it correctly, the click can register in the wrong place.

Conclusions

CSS cursors are a small tool with a real impact on user experience when used thoughtfully. It's not about decorating for the sake of decorating: a well-chosen cursor communicates affordance, guides interaction, and makes your interface feel more intuitive and professional.

As I learned when I started experimenting with them, details make all the difference: good fallbacks, compatible formats, correct sizes, a well-defined hotspot, and always considering whether the user's device has a cursor in the first place. If you apply these practices, you will notice that your interface gains clarity effortlessly.

And remember: the best cursor is the one the user doesn't need to think about. If it's doing its job, it's invisible.

Everything you need to know about CSS cursors in one guide: the 30+ predefined types, how to use `url()` for custom cursors (PNG, SVG, or CUR), how to define the hotspot, the best fallbacks, and the mistakes to avoid so you don't break the user experience.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.