A practical guide to building a basic custom cursor using pure CSS—no JavaScript involved—including plain CSS and Tailwind CSS approaches, with clear real-world limitations.
Before talking about animations, inertia, or fancy trailing effects, it’s important to understand the hard limits of custom cursors on the web:
CSS can change the cursor’s appearance, but not its behavior.
That means:
But CSS is perfect for:
This article focuses strictly on basic, CSS-only custom cursors.
Try it live: View Demo | Download HTML (right-click → Save As)
CSS exposes a single property for this:
cursor: ...;The value can be:
pointer, grab, etc.)url(...)The simplest example:
body { cursor: pointer;}This is rarely recommended, but it illustrates the idea.
This is the most common and production-safe way to build a custom cursor using CSS.
Recommended specs:
png or svgExample file name: cursor-dot.png
body { cursor: url('/cursor-dot.png') 16 16, default;}Explanation:
16 16 defines the hotspot (click position)default acts as a fallbacka,button { cursor: url('/cursor-hover.png') 16 16, pointer;}This is pure CSS, zero JavaScript, and extremely stable in production.
body { cursor: url('/cursor-dot.svg') 8 8, default;}
a:hover,button:hover { cursor: url('/cursor-ring.svg') 12 12, pointer;}This style works well for:
With CSS alone, you cannot:
❌ Create trailing effects
❌ Add easing or interpolation
❌ Follow real-time pointer position
❌ Build context-aware cursor behavior
With CSS alone, you can:
✅ Customize cursor visuals
✅ Define hover affordances
✅ Maintain visual consistency
✅ Avoid performance overhead
If you need behavior, JavaScript is not optional.
Tailwind does not extend cursor capabilities—it only changes how you author styles.
<div class="cursor-[url('/cursor-dot.png')_16_16,_default]">Content</div>Hover state:
<a class="cursor-[url('/cursor-dot.png')_16_16,_default] hover:cursor-[url('/cursor-ring.png')_16_16,_pointer]"> Link</a>Note: underscores (
_) represent spaces
Define reusable cursor utilities instead of cluttering markup.
tailwind.config.jsmodule.exports = { theme: { extend: { cursor: { dot: "url('/cursor-dot.png') 16 16, default", ring: "url('/cursor-ring.png') 16 16, pointer", }, }, },};<body class="cursor-dot"> <a class="hover:cursor-ring">Link</a> <button class="hover:cursor-ring">Button</button></body>This approach is clean, reusable, and scalable.
Always account for non-mouse input:
@media (pointer: coarse) { body { cursor: auto; }}Avoid:
The cursor is a navigation tool—not decoration.
CSS-only cursors make sense when:
Use JavaScript when:
A complete, standalone HTML demo is available:
The demo file shows both plain CSS and data URI implementations. You can open it directly in your browser or use it as a reference for your own projects.
CSS-only custom cursors are simple, honest, and reliable.
They do one thing well: replace the cursor visually without introducing performance or maintenance risk. For many editorial sites and landing pages, this is exactly the right level of customization.
If you need more than this, that's not a limitation of CSS—it's a signal that JavaScript belongs in the solution.
No comments yet
Loading comments...