CURSOR EFFECTS

Cursor Ring Hover Effect

A custom cursor ring that follows the pointer and expands when hovering over interactive elements inside the preview area.

Hover Link
HTML
<div class="cursor-ring-area">

  <div
    class="cursor-ring"
    data-cursor-ring
  ></div>

  <div class="cursor-ring-items">

    <button class="cursor-ring-item">
      Hover Button
    </button>

    <a
      href="#"
      class="cursor-ring-item"
    >
      Hover Link
    </a>

  </div>

</div>
CSS
.cursor-ring-area{
  position:relative;
  width:100%;
  min-height:220px;

  display:flex;
  align-items:center;
  justify-content:center;

  overflow:hidden;
  cursor:none;
}

.cursor-ring{
  position:absolute;
  top:0;
  left:0;
  z-index:10;

  width:34px;
  height:34px;

  border:2px solid #22d3ee;
  border-radius:50%;

  background:rgba(34,211,238,.03);

  pointer-events:none;

  opacity:0;

  transform:
    translate(-50%, -50%);

  box-shadow:
    0 0 18px rgba(34,211,238,.22);

  transition:
    width .18s ease,
    height .18s ease,
    background .18s ease,
    border-color .18s ease,
    opacity .15s ease;
}

.cursor-ring-area:hover .cursor-ring{
  opacity:1;
}

.cursor-ring.active{
  width:64px;
  height:64px;

  background:
    rgba(34,211,238,.10);

  border-color:#8b5cf6;

  box-shadow:
    0 0 28px rgba(139,92,246,.25);
}

.cursor-ring-items{
  position:relative;
  z-index:2;

  display:flex;
  align-items:center;
  justify-content:center;
  flex-wrap:wrap;

  gap:18px;
}

.cursor-ring-item{
  padding:14px 20px;

  border:1px solid #263750;
  border-radius:10px;

  background:#0b1423;

  color:#ffffff;

  font-size:14px;
  font-weight:700;

  text-decoration:none;

  cursor:none;

  transition:
    background .2s ease,
    border-color .2s ease,
    transform .2s ease;
}

.cursor-ring-item:hover{
  background:#101c2e;
  border-color:#22d3ee;

  transform:
    translateY(-2px);
}
JavaScript
const area =
  document.querySelector('.cursor-ring-area');

if (area) {

  const ring =
    area.querySelector('[data-cursor-ring]');

  area.addEventListener(
    'mousemove',
    function(event) {

      if (!ring) {
        return;
      }

      const rect =
        area.getBoundingClientRect();

      ring.style.left =
        event.clientX -
        rect.left +
        'px';

      ring.style.top =
        event.clientY -
        rect.top +
        'px';

    }
  );


  area.addEventListener(
    'mouseover',
    function(event) {

      const item =
        event.target.closest(
          '.cursor-ring-item'
        );

      if (!item) {
        return;
      }

      if (!ring) {
        return;
      }

      ring.classList.add(
        'active'
      );

    }
  );


  area.addEventListener(
    'mouseout',
    function(event) {

      const item =
        event.target.closest(
          '.cursor-ring-item'
        );

      if (!item) {
        return;
      }

      if (item.contains(event.relatedTarget)) {
        return;
      }

      if (!ring) {
        return;
      }

      ring.classList.remove(
        'active'
      );

    }
  );


  area.addEventListener(
    'click',
    function(event) {

      const link =
        event.target.closest(
          'a.cursor-ring-item'
        );

      if (!link) {
        return;
      }

      event.preventDefault();

    }
  );

}

How This Component Works

This component replaces the default mouse pointer with a custom ring that tracks cursor movement in real time. The HTML sets up a container (cursor-ring-area) holding two things: a single ring element (cursor-ring) and a group of interactive items — a button and a link — that the ring reacts to.

The JavaScript listens for mousemove on the entire document, but only acts when the cursor is inside a .cursor-ring-area container. On every movement, it calculates the cursor’s position relative to that container and updates the ring’s left and top values directly, so the ring follows the pointer smoothly. Because the CSS also applies a translate(-50%, -50%) transform, the ring stays perfectly centered on the cursor rather than trailing from its top-left corner.

The “hover to expand” behavior comes from two more event listeners: mouseover and mouseout. Internally, the script uses the closest() method to detect whether the hovered element is a cursor-ring-item (the button or link), then JavaScript adds an active class to the ring.

Customization Tips

Everything about this cursor ring hover effect can be adjusted from the CSS file without touching the JavaScript:

  • Change the default ring color — update the border and box-shadow colors in .cursor-ring (currently cyan).
  • Change the “active” hover color — update border-color and box-shadow inside .cursor-ring.active (currently purple) to match your brand.
  • Adjust the expand size — increase or decrease the width/height values in .cursor-ring.active for a bigger or smaller expansion effect.
  • Control animation speed — the transition durations (currently .18s) can be shortened for a snappier feel or lengthened for a smoother one.
  • Style the interactive items — the button and link styling live in .cursor-ring-item, independent of the ring itself.

Where to Use This

Because this effect is built around hover states on specific elements, it works best wherever you want to draw attention to clickable items:

  • Navigation menus and buttons, where the ring naturally highlights which item the user is about to click.
  • Portfolio or agency websites, especially around call-to-action buttons like “Contact” or “View Work.”
  • Interactive link lists, such as a set of social or project links, where the expanding ring gives clear visual feedback.

This effect is not meant to be used across large blocks of text or content-heavy sections — it’s designed specifically for discrete, clickable elements, not full-page tracking.

If you’d prefer a softer, glowing trail instead of a ring, take a look at our Gradient Cursor Trail component for a similar hover-based approach with a different visual style.

Frequently Asked Questions

Does this work on touch devices?
No, and it’s designed not to. Since touch devices don’t have a hover state or a visible cursor, the component automatically hides the ring and restores the normal cursor on smaller screens via a mobile media query, so nothing breaks or looks out of place.

Can I add more interactive items to the ring’s hover detection?
Yes. Any element you give the class cursor-ring-item will automatically trigger the ring’s expand effect on hover — you don’t need to modify the JavaScript to add more buttons or links.

Why does the ring sometimes lag slightly behind fast mouse movements?
This is expected with any cursor-following effect built on the mousemove event, since the ring’s position updates on each event fire rather than continuously. The lag is minimal and generally not noticeable during normal use.

What makes this cursor ring hover effect different from a simple cursor dot?
Unlike a basic dot that just follows the mouse, this cursor ring hover effect reacts to what’s underneath it — expanding, changing color, and glowing only when hovering over clickable items like buttons or links. This gives users clear visual feedback about what’s interactive on the page.

Scroll to Top