Text Hover Cursor
A custom cursor label that follows the mouse and changes its text when hovering over interactive items inside the preview area.
<div class="text-cursor-area">
<div
class="text-hover-cursor"
data-text-cursor
>
VIEW
</div>
<div class="text-cursor-items">
<a
href="#"
class="text-cursor-item"
data-cursor-text="OPEN"
>
Project One
</a>
<a
href="#"
class="text-cursor-item"
data-cursor-text="VIEW"
>
Project Two
</a>
<a
href="#"
class="text-cursor-item"
data-cursor-text="EXPLORE"
>
Project Three
</a>
</div>
</div>
.text-cursor-area{
position:relative;
width:100%;
min-height:220px;
display:flex;
align-items:center;
justify-content:center;
overflow:hidden;
cursor:none;
}
.text-hover-cursor{
position:absolute;
top:0;
left:0;
min-width:58px;
height:58px;
display:flex;
align-items:center;
justify-content:center;
padding:0 12px;
border-radius:50%;
background:#22d3ee;
color:#050914;
font-size:10px;
font-weight:800;
letter-spacing:1px;
pointer-events:none;
transform:translate(-50%, -50%);
box-shadow:
0 0 22px rgba(34,211,238,.28);
z-index:9999;
transition:
min-width .2s ease,
background .2s ease;
}
.text-cursor-items{
position:relative;
z-index:1;
display:flex;
align-items:center;
justify-content:center;
flex-wrap:wrap;
gap:16px;
}
.text-cursor-item{
padding:14px 18px;
border:1px solid #263750;
border-radius:10px;
background:#0b1423;
color:#ffffff;
text-decoration:none;
font-size:14px;
font-weight:700;
cursor:none;
transition:
border-color .2s ease,
background .2s ease,
transform .2s ease;
}
.text-cursor-item:hover{
border-color:#22d3ee;
background:#101c2e;
transform:translateY(-2px);
}
const area =
document.querySelector('.text-cursor-area');
const cursor =
document.querySelector('[data-text-cursor]');
if (area) {
area.addEventListener(
'mousemove',
function(event) {
if (!cursor) {
return;
}
const rect =
area.getBoundingClientRect();
cursor.style.left =
event.clientX -
rect.left +
'px';
cursor.style.top =
event.clientY -
rect.top +
'px';
}
);
area.addEventListener(
'mouseover',
function(event) {
const item =
event.target.closest(
'[data-cursor-text]'
);
if (!item) {
return;
}
if (!cursor) {
return;
}
cursor.textContent =
item.getAttribute(
'data-cursor-text'
);
}
);
area.addEventListener(
'mouseout',
function(event) {
const item =
event.target.closest(
'[data-cursor-text]'
);
if (!item) {
return;
}
if (!cursor) {
return;
}
cursor.textContent =
'VIEW';
}
);
}
How This Text Hover Cursor Works
This effect replaces the default cursor with a small circular label that changes its text depending on which link the user is hovering over — a common technique on modern portfolio sites where hovering a project reveals a contextual action word like “VIEW” or “OPEN.” The HTML sets up a container (text-cursor-area) holding the custom cursor element (text-hover-cursor) and a set of links, each carrying its own label through a data-cursor-text attribute.
The JavaScript manages three things. A mousemove listener keeps the circular cursor positioned exactly where the mouse is, .using the same getBoundingClientRect() coordinate-calculation approach seen in other cursor effects on this site — subtracting the container’s position from the mouse’s screen position.. A mouseover listener checks whether the cursor has entered a link with a data-cursor-text attribute, and if so, updates the cursor’s visible text to match that specific link’s value. A mouseout listener resets the text back to the default “VIEW” label once the mouse leaves the link.
Visually, the cursor circle is built with border-radius: 50% and a fixed height, but its min-width is flexible — this matters because words like “EXPLORE” are longer than “VIEW,” so the circle needs to stretch horizontally to fit the text without looking cramped. A CSS transition on min-width makes this resizing smooth rather than an abrupt jump.
Customizing This Text Hover Cursor
This effect can be adjusted in a few key places without editing the JavaScript:
- Change the cursor’s default text and color — update the text inside
.text-hover-cursorin the HTML, and adjustbackground/colorin the CSS to match your brand. - Add unique labels per link — since each link’s cursor text comes from its own
data-cursor-textattribute, you can set completely different words for each item without changing any script logic. - Adjust the cursor size — modify
min-widthandheightin.text-hover-cursorif your labels tend to be longer or shorter than the examples shown. - Change the resize speed — the
transitionduration onmin-widthcontrols how quickly the circle stretches to fit new text.
Where to Use This Text Hover Cursor
Since this effect works by displaying contextual text per link, it’s especially useful for:
- Portfolio and project grids, where each item can show a different action word like “VIEW,” “OPEN,” or “EXPLORE” depending on what clicking it will do.
- Case study listings, giving visitors a quick visual cue about what kind of content awaits behind each link.
- Creative agency websites, where a custom-text cursor reinforces a polished, interactive brand feel.
This effect isn’t ideal for standard navigation menus or forms, since replacing the cursor entirely can feel disruptive in more functional, task-focused parts of a website.
If you’d like a similar effect that shows images instead of text, check out our Image Reveal Cursor component for a visual alternative.
Frequently Asked Questions
Can each link show a completely different word, or are the options limited?
Each link can show any word you want, since the label comes directly from that link’s data-cursor-text attribute rather than a fixed list defined in the JavaScript. You could use single words, short phrases, or even icons represented as text.
What happens if a link doesn’t have a data-cursor-text attribute?
If a link inside the container doesn’t include this attribute, hovering over it simply won’t update the cursor’s text — it will continue showing whatever label was set previously, or the default “VIEW” text if no other link has been hovered yet.
Does this replace the browser’s default cursor everywhere on the page?
No, only within the specific container it’s applied to. The cursor: none property is scoped to .text-cursor-area, so the custom cursor only appears while the mouse is inside that section — everywhere else on the page, the normal system cursor remains active.
