Tailwind CSS Cursor
The cursor
property controls how the mouse pointer appears when it hovers over an element. This property allows developers to define how users interact visually with elements in a user interface.
With Tailwind's utility classes, it becomes even simpler to control the cursor. In this guide, we will learn every step of working with cursor styling in Tailwind CSS.
Class | Properties | Example |
---|---|---|
cursor-auto | cursor: auto; | <div className="cursor-auto"></div> |
cursor-default | cursor: default; | <div className="cursor-default"></div> |
cursor-pointer | cursor: pointer; | <div className="cursor-pointer"></div> |
cursor-wait | cursor: wait; | <div className="cursor-wait"></div> |
cursor-text | cursor: text; | <div className="cursor-text"></div> |
cursor-move | cursor: move; | <div className="cursor-move"></div> |
cursor-help | cursor: help; | <div className="cursor-help"></div> |
cursor-not-allowed | cursor: not-allowed; | <div className="cursor-not-allowed"></div> |
cursor-none | cursor: none; | <div className="cursor-none"></div> |
cursor-context-menu | cursor: context-menu; | <div className="cursor-context-menu"></div> |
cursor-progress | cursor: progress; | <div className="cursor-progress"></div> |
cursor-cell | cursor: cell; | <div className="cursor-cell"></div> |
cursor-crosshair | cursor: crosshair; | <div className="cursor-crosshair"></div> |
cursor-vertical-text | cursor: vertical-text; | <div className="cursor-vertical-text"></div> |
cursor-alias | cursor: alias; | <div className="cursor-alias"></div> |
cursor-copy | cursor: copy; | <div className="cursor-copy"></div> |
cursor-no-drop | cursor: no-drop; | <div className="cursor-no-drop"></div> |
cursor-grab | cursor: grab; | <div className="cursor-grab"></div> |
cursor-grabbing | cursor: grabbing; | <div className="cursor-grabbing"></div> |
cursor-all-scroll | cursor: all-scroll; | <div className="cursor-all-scroll"></div> |
cursor-col-resize | cursor: col-resize; | <div className="cursor-col-resize"></div> |
cursor-row-resize | cursor: row-resize; | <div className="cursor-row-resize"></div> |
cursor-n-resize | cursor: n-resize; | <div className="cursor-n-resize"></div> |
cursor-e-resize | cursor: e-resize; | <div className="cursor-e-resize"></div> |
cursor-s-resize | cursor: s-resize; | <div className="cursor-s-resize"></div> |
cursor-w-resize | cursor: w-resize; | <div className="cursor-w-resize"></div> |
cursor-ne-resize | cursor: ne-resize; | <div className="cursor-ne-resize"></div> |
cursor-nw-resize | cursor: nw-resize; | <div className="cursor-nw-resize"></div> |
cursor-se-resize | cursor: se-resize; | <div className="cursor-se-resize"></div> |
cursor-sw-resize | cursor: sw-resize; | <div className="cursor-sw-resize"></div> |
cursor-ew-resize | cursor: ew-resize; | <div className="cursor-ew-resize"></div> |
cursor-ns-resize | cursor: ns-resize; | <div className="cursor-ns-resize"></div> |
cursor-nesw-resize | cursor: nesw-resize; | <div className="cursor-nesw-resize"></div> |
cursor-nwse-resize | cursor: nwse-resize; | <div className="cursor-nwse-resize"></div> |
cursor-zoom-in | cursor: zoom-in; | <div className="cursor-zoom-in"></div> |
cursor-zoom-out | cursor: zoom-out; | <div className="cursor-zoom-out"></div> |
Overview of Cursor
Adding the Cursor
To start, you can set the mouse pointer style directly on any HTML element by using Tailwind's cursor utilities. These classes correspond to CSS cursor functionalities like pointer
, not-allowed
, wait
, and more. Below is how you can use the cursor
property:
// file: App.jsx export default function App() { return ( <div className="h-screen w-screen flex items-center justify-center flex-col gap-8"> {/* Buttons with different cursors */} <button className="cursor-pointer bg-blue-500 w-32 text-white py-2 px-4 rounded"> Pointer </button> <button className="cursor-not-allowed bg-blue-500 w-32 text-white py-2 px-4 rounded"> Not Allowed </button> <button className="cursor-wait bg-blue-500 text-white py-2 w-32 px-4 rounded"> Wait </button> </div> ); }
States and Responsiveness
Hover and Focus States
Tailwind allows you to change cursor styles dynamically based on user interactions. In Tailwind CSS, this is made easy by using state-based modifiers like hover:
or focus:
. For example, below is a pointer cursor that becomes wait on focus
:
// file: App.jsx export default function App() { return ( <div className="h-screen w-screen flex items-center justify-center"> <button className="cursor-pointer focus:cursor-wait bg-gray-700 text-white py-2 px-4 rounded"> Try Hovering or Focusing </button> </div> ); }
Breakpoint Modifiers
You can also style cursors responsively based on screen size using Tailwind's breakpoint modifiers like sm
, md
, lg
, etc. This enables you to adapt the user experience according to device dimensions fluidly.
// file: App.jsx export default function App() { return ( <div className="h-screen w-screen flex flex-col items-center justify-center space-y-6 bg-gradient-to-br from-gray-50 to-gray-200"> <div className="text-lg">Resize your browser to see changes!</div> {/* Cursor changes based on breakpoints */} <button className="sm:cursor-pointer md:cursor-wait lg:cursor-crosshair bg-green-600 text-white py-2 px-4 rounded"> Responsive Cursor </button> </div> ); }
Custom Cursor
While Tailwind comes with several predefined options, customizing cursor styles to match specific design needs or branding is incredibly easy. You can implement custom cursors by extending Tailwind's configuration or through arbitrary values.
Extending the Theme
Tailwind's configuration file (usually tailwind.config.js
) allows us to extend default cursor options. This proves useful when you want to add unique styles for various use cases. After extending the configuration, use these utilities like any other Tailwind class:
import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; // file: App.jsx export default function App() { return ( <div className="h-screen w-screen flex flex-col items-center justify-center space-y-6"> <div className="cursor-custom text-blue-500 underline text-center px-10"> Hover on this text to see the below image as the cursor </div> <img src="https://images.unsplash.com/photo-1517059224940-d4af9eec41b7" alt="Office Desk" /> </div> ); }
Using Arbitrary Values
In scenarios where predefined values are insufficient and theme configuration is too much effort, arbitrary values come into play. Tailwind lets you define cursor values on-the-fly by wrapping the desired CSS property in square brackets.
// file: App.jsx export default function App() { return ( <div className="h-screen w-screen flex flex-col items-center justify-center space-y-6"> <div className="cursor-[url(https://images.unsplash.com/photo-1517059224940-d4af9eec41b7?w=20),pointer] text-blue-500 underline text-center px-10"> Hover on this text to see the below image as the cursor </div> <img src="https://images.unsplash.com/photo-1517059224940-d4af9eec41b7" alt="Office Desk" /> </div> ); }
Real World Examples
Interactive Product Cards Grid
This example shows a grid of product cards with different cursor styles based on product availability:
export default function ProductGrid() { const products = [ { id: 1, name: "Premium Leather Backpack", price: 129.99, available: true, src: "https://images.unsplash.com/photo-1548036328-c9fa89d128fa", alt: "Brown leather backpack" }, { id: 2, name: "Wireless Headphones", price: 199.99, available: false, src: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e", alt: "Black wireless headphones" }, { id: 3, name: "Smart Watch Series X", price: 299.99, available: true, src: "https://images.unsplash.com/photo-1579586337278-3befd40fd17a", alt: "Smart watch with black band" }, { id: 4, name: "Mechanical Keyboard", price: 159.99, available: true, src: "https://images.unsplash.com/photo-1587829741301-dc798b83add3", alt: "RGB mechanical keyboard" }, { id: 5, name: "4K Monitor", price: 399.99, available: false, src: "https://images.unsplash.com/photo-1527443224154-c4a3942d3acf", alt: "Ultra-wide monitor" }, { id: 6, name: "Gaming Mouse", price: 79.99, available: true, src: "https://images.unsplash.com/photo-1527864550417-7fd91fc51a46", alt: "Gaming mouse with RGB lighting" } ]; return ( <div className="grid gap-6 p-8"> {products.map((product) => ( <div key={product.id} className={`p-4 border rounded-lg ${ product.available ? "cursor-pointer hover:shadow-lg" : "cursor-not-allowed opacity-60" }`} > <img src={product.src} alt={product.alt} className="w-full h-48 object-cover rounded" /> <h3 className="mt-2 font-bold">{product.name}</h3> <p className="text-gray-600">${product.price}</p> <button className={`mt-2 px-4 py-2 rounded ${ product.available ? "bg-blue-500 text-white cursor-pointer" : "bg-gray-300 cursor-not-allowed" }`} > {product.available ? "Add to Cart" : "Out of Stock"} </button> </div> ))} </div> ); }
Custom File Upload Interface
This example demonstrates a file upload interface with different cursor states for drag and drop functionality.
export default function FileUpload() { const files = [ { id: 1, name: "presentation.pptx", size: "2.4 MB", type: "document", status: "uploaded" }, { id: 2, name: "budget.xlsx", size: "1.8 MB", type: "spreadsheet", status: "uploading" }, { id: 3, name: "profile.jpg", size: "3.2 MB", type: "image", status: "failed" }, { id: 4, name: "report.pdf", size: "5.1 MB", type: "document", status: "uploaded" }, { id: 5, name: "video.mp4", size: "15.7 MB", type: "video", status: "uploading" }, { id: 6, name: "archive.zip", size: "8.3 MB", type: "archive", status: "uploaded" } ]; return ( <div className="p-6"> <div className="border-2 border-dashed border-gray-300 rounded-lg p-8 cursor-copy hover:border-blue-500 transition-colors"> <div className="text-center"> <p className="text-gray-600">Drag and drop files here</p> <button className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> Browse Files </button> </div> </div> <div className="mt-6 space-y-3"> {files.map((file) => ( <div key={file.id} className={`flex items-center justify-between p-3 rounded ${ file.status === 'uploading' ? 'bg-blue-50 cursor-progress' : file.status === 'failed' ? 'bg-red-50 cursor-not-allowed' : 'bg-green-50 cursor-pointer' }`} > <div className="flex items-center gap-3"> <span className="text-gray-600">{file.name}</span> <span className="text-sm text-gray-500">{file.size}</span> </div> <span className="text-sm capitalize">{file.status}</span> </div> ))} </div> </div> ); }
Interactive Table with Sorting
This example shows a table with sortable columns and various cursor interactions:
export default function DataTable() { const tableData = [ { id: 1, name: "John Smith", role: "Frontend Developer", status: "Active", lastActive: "2 hours ago", projects: 8, avatar: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e" }, { id: 2, name: "Sarah Johnson", role: "UX Designer", status: "Away", lastActive: "1 day ago", projects: 12, avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80" }, { id: 3, name: "Michael Brown", role: "Backend Developer", status: "Active", lastActive: "5 mins ago", projects: 5, avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e" }, { id: 4, name: "Emily Davis", role: "Project Manager", status: "Offline", lastActive: "3 days ago", projects: 15, avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330" }, { id: 5, name: "David Wilson", role: "DevOps Engineer", status: "Active", lastActive: "1 hour ago", projects: 7, avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d" }, { id: 6, name: "Lisa Anderson", role: "QA Engineer", status: "Away", lastActive: "4 hours ago", projects: 9, avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80" } ]; return ( <table className="w-full border-collapse"> <thead> <tr className="bg-gray-100"> <th className="p-4 text-left cursor-pointer hover:bg-gray-200"> Name ↕ </th> <th className="p-4 text-left cursor-pointer hover:bg-gray-200"> Role ↕ </th> <th className="p-4 text-left cursor-pointer hover:bg-gray-200"> Status ↕ </th> <th className="p-4 text-left cursor-pointer hover:bg-gray-200"> Last Active ↕ </th> <th className="p-4 text-left cursor-pointer hover:bg-gray-200"> Projects ↕ </th> </tr> </thead> <tbody> {tableData.map((user) => ( <tr key={user.id} className="border-b cursor-pointer hover:bg-gray-50" > <td className="p-4"> <div className="flex items-center"> <img src={user.avatar} alt={user.name} className="w-8 h-8 rounded-full mr-3" /> {user.name} </div> </td> <td className="p-4">{user.role}</td> <td className="p-4"> <span className={`px-2 py-1 rounded-full text-sm ${ user.status === "Active" ? "bg-green-100 text-green-800" : user.status === "Away" ? "bg-yellow-100 text-yellow-800" : "bg-gray-100 text-gray-800" }`} > {user.status} </span> </td> <td className="p-4">{user.lastActive}</td> <td className="p-4">{user.projects}</td> </tr> ))} </tbody> </table> ); }
Interactive Image Gallery
This example shows an image gallery with different cursor states for interactions:
export default function ImageGallery() { const images = [ { id: 1, src: "https://images.unsplash.com/photo-1682687220945-922198770e60", alt: "Mountain landscape", liked: true, downloads: 1240 }, { id: 2, src: "https://images.unsplash.com/photo-1682687220198-88e9bdea9931", alt: "City streets", liked: true, downloads: 2103 }, { id: 3, src: "https://images.unsplash.com/photo-1682687220509-61b8a906ca19", alt: "Desert dunes", liked: true, downloads: 989 }, { id: 4, src: "https://images.unsplash.com/photo-1682687220923-c58b9a4592ae", alt: "Snow peaks", liked: false, downloads: 1432 } ]; return ( <div className="p-8"> <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> {images.map((image) => ( <div key={image.id} className="relative group cursor-zoom-in" > <img src={image.src} alt={image.alt} className="w-full h-64 object-cover rounded-lg" /> <div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-50 transition-all duration-300"> <div className="absolute bottom-4 left-4 right-4 text-white opacity-0 group-hover:opacity-100"> <div className="flex justify-between items-center"> <button className={`cursor-pointer ${ image.liked ? "text-red-500" : "text-white" }`} > ❤️ {image.liked ? "Liked" : "Like"} </button> <button className="cursor-pointer hover:text-blue-400"> 💾 Download </button> </div> <p className="mt-2 text-sm"> {image.downloads.toLocaleString()} downloads </p> </div> </div> </div> ))} </div> </div> ); }
Interactive Rating Component
This example demonstrates a rating component with different cursor interactions:
export default function ProductRatings() { const reviews = [ { id: 1, product: "Wireless Earbuds Pro", rating: 4, comment: "Great sound quality, comfortable fit", helpful: 45, date: "2024-01-15" }, { id: 2, product: "Smart Home Hub", rating: 5, comment: "Perfect for home automation", helpful: 89, date: "2024-01-14" }, { id: 3, product: "Fitness Tracker", rating: 3, comment: "Battery life could be better", helpful: 23, date: "2024-01-13" }, { id: 4, product: "Portable Charger", rating: 5, comment: "Fast charging, compact design", helpful: 67, date: "2024-01-12" }, { id: 5, product: "Bluetooth Speaker", rating: 4, comment: "Impressive bass, good battery life", helpful: 34, date: "2024-01-11" }, { id: 6, product: "Wireless Mouse", rating: 5, comment: "Responsive and ergonomic", helpful: 56, date: "2024-01-10" } ]; return ( <div className="max-w-2xl mx-auto p-8"> {reviews.map((review) => ( <div key={review.id} className="mb-6 p-4 border rounded-lg"> <h3 className="font-bold text-lg">{review.product}</h3> <div className="flex items-center my-2"> {[1, 2, 3, 4, 5].map((star) => ( <span key={star} className={`cursor-pointer text-2xl ${ star <= review.rating ? "text-yellow-400" : "text-gray-300" }`} > ★ </span> ))} </div> <p className="text-gray-600 my-2">{review.comment}</p> <div className="flex items-center justify-between mt-4"> <div className="flex items-center"> <button className="cursor-pointer text-gray-500 hover:text-blue-500"> 👍 Helpful ({review.helpful}) </button> <button className="cursor-pointer text-gray-500 hover:text-red-500 ml-4"> Report </button> </div> <span className="text-sm text-gray-400 cursor-help" title="Review date"> {review.date} </span> </div> </div> ))} </div> ); }
Customization Examples
Custom Text Selection Cursor
Implement a text editor interface with a custom cursor for different text selection states.
import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; // App.js export default function TextEditorCursor() { return ( <div className="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-xl"> <div className="space-y-6"> <div className="cursor-text hover:cursor-text-edit"> <h1 className="text-3xl font-bold text-gray-800"> Interactive Document Editor </h1> <p className="mt-4 text-gray-600 leading-relaxed"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco. </p> </div> <div className="border-l-4 border-blue-500 pl-4 cursor-text"> <blockquote className="text-lg text-gray-700 italic"> "The best way to predict the future is to create it." </blockquote> </div> </div> </div> ) }
Custom Image Cursor for Interactive Gallery
A photo gallery component that changes cursor to a magnifying glass on hover, perfect for image exploration interfaces.
import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; // GalleryWithCustomCursor.js import React, { useState } from 'react'; const GalleryWithCustomCursor = () => { const [selectedImage, setSelectedImage] = useState(null); const images = [ 'https://images.unsplash.com/photo-1461988320302-91bde64fc8e4?w=400&h=300', 'https://images.unsplash.com/photo-1461988625982-7e46a099bf4f?w=400&h=300', ]; return ( <div className="p-8 bg-gray-100 min-h-screen"> <div className="grid grid-cols-2 gap-4"> {images.map((image, index) => ( <div key={index} className="relative group cursor-magnify transition-transform duration-300 hover:scale-105" onClick={() => setSelectedImage(image)} > <img src={image} alt={`Gallery item ${index + 1}`} className="w-full h-64 object-cover rounded-lg shadow-lg" /> <div className="absolute inset-0 bg-black opacity-0 group-hover:opacity-20 transition-opacity duration-300 rounded-lg" /> </div> ))} </div> {selectedImage && ( <div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-8"> <img src={selectedImage} alt="Selected" className="max-w-4xl max-h-[80vh] object-contain" /> <button onClick={() => setSelectedImage(null)} className="absolute top-4 right-4 text-white text-xl" > ✕ </button> </div> )} </div> ); }; export default GalleryWithCustomCursor;
Gradient Progress Cursor
A reading progress component that changes cursor based on scroll position, ideal for blog posts and articles.
import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; // ReadingProgressCursor.js import React, { useState, useEffect } from 'react'; const ReadingProgressCursor = () => { const [scrollProgress, setScrollProgress] = useState(0); useEffect(() => { const handleScroll = () => { const totalHeight = document.documentElement.scrollHeight - window.innerHeight; const progress = (window.scrollY / totalHeight) * 100; setScrollProgress(progress); }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); const getCursorClass = () => { if (scrollProgress < 33) return 'cursor-progress-start'; if (scrollProgress < 66) return 'cursor-progress-middle'; return 'cursor-progress-end'; }; return ( <article className={`p-8 max-w-2xl mx-auto ${getCursorClass()}`}> <h1 className="text-4xl font-bold mb-6">The Art of Cursor Design</h1> <div className="prose prose-lg"> <p className="mb-4"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. </p> {/* Add more paragraphs to enable scrolling */} {Array(10).fill().map((_, i) => ( <p key={i} className="mb-4"> Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> ))} </div> </article> ); }; export default ReadingProgressCursor;
Best Practices
Maintain Design Consistency
When building an interface with Tailwind CSS, ensuring consistent cursor styling is critical for a consistent user experience. Cursors should complement the interactive elements they represent, maintaining uniformity across buttons, links, and card elements. For instance, using cursor-pointer
on all clickable items creates a predictable and seamless interaction model for users without confusion or unintended behaviors.
Ensure that all interactive elements, like buttons, links, and form inputs, have a cursor style that matches their expected behavior. For example, buttons use cursor-pointer
, disabled buttons use cursor-not-allowed
, and non-interactive containers default to cursor-auto
. This simple yet effective approach enforces consistency and clarity for both designers and end-users.
Leverage Utility Combinations
Tailwind utilities thrive on being combined to achieve highly customized interfaces while keeping your codebase maintainable. For cursor-specific classes, pairing them with layout or spacing utilities is an effective way to meet both functional and aesthetic needs. For instance, you might group cursor-pointer
with hover transitions (transition-colors
and hover:bg-gray-100
) to create interactive buttons.
Using consistent utility combinations also helps highlight semantic meaning. If you plan to draw attention to interactive elements that perform a destructive action, consider pairing cursor-pointer
with a red color scheme and distinct hover states. This coordinated usage can guide users’ eyes toward interactive regions more naturally and emphasize key functionalities.
Accessibility Considerations
Enhance Readability and Navigability
Cursor styles may seem like a subtle aspect of your interface, but they can significantly impact how users read and navigate content. By providing clear visual cues, users immediately know where to click or tap, reducing guesswork and friction. This clarity helps everyone, including individuals who may struggle with complex layouts or smaller screens.
Readability is often influenced by spacing and contrast, but cursor indicators add another layer of feedback. For instance, ensuring that every interactive headline uses cursor-pointer
alongside a distinct text color can help users visually parse content sections. This small but meaningful addition can improve how easily people navigate through dense or multi-layered interfaces.
Support Accessible Interactive Elements
Interactive components—from buttons to dropdown menus—rely on visual cues to guide user actions. Applying cursor utilities in a thoughtful way can make these elements more accessible. For example, a toggle switch that uses cursor-pointer
for the actionable part and cursor-default
for the label helps users understand exactly which region controls the action.
It’s also beneficial to consistently style focus states when dealing with interactive elements. While the cursor indicates hover or pointer presence, keyboard users rely on outlines. By providing both clear cursor and well-defined outlines, you meet a wide range of accessibility needs.