Advertisement

Dynamic Article Hover Cursor

| by Vladimir | 2 min read | code by Jhey
A11y Ready Intermediate

Tech & Dependencies

HTML CSS Babel

Features

  • Custom Cursor
  • CSS :has()
  • Blend Modes
  • Grid Layout

Browser Support

Chrome 105+ Edge 105+ Firefox 121+ Safari 15.4+

Core

The mouse cursor is the primary bridge between user and interface, yet it often remains a passive arrow. This component reimagines the cursor as an active participant in the narrative. By morphing the pointer into a contextual “Read” badge that reacts to specific content, we reduce cognitive load and create a more intent-driven browsing experience. It feels less like clicking links and more like exploring a curated magazine.

Core Technique

The elegance of this solution lies in its reliance on modern CSS pseudo-classes rather than complex JavaScript event listeners.

  • CSS Brain (:has): The logic is almost entirely CSS-driven. :root:has(article:hover) detects when any article is hovered, triggering the cursor’s appearance. Specific rules like :root:has(article:nth-of-type(odd):hover) even change the cursor’s color dynamically based on the item type.
  • Dual Cursor System: To ensure legibility against any background, two cursors are rendered: a colored base with mix-blend-mode: difference and a text overlay. This ensures high contrast regardless of the image underneath.
  • Performance: JavaScript is used only to update the --x and --y coordinates. All styling, scaling, and color transitions are handled by the compositor thread via CSS, ensuring 60fps performance.

Customization

The visual language is strictly separated from the interaction logic. You can easily adapt the grid or the cursor style.

Changing Cursor Appearance

:is(.cursor, .cursor__text) {
  /* Change shape to square */
  border-radius: 4px; 
  /* Adjust size */
  font-size: 1.2rem; 
}

Grid Layout Tweaks

@media(min-width: 600px) {
  body {
    /* Switch to 3 columns */
    grid-template-columns: repeat(3, 1fr); 
    gap: 2rem;
  }
}

Browser Support

The core functionality depends on :has(), which is well-supported in all evergreen browsers.

Advertisement