Custom Cursor

A soft dot that trails the mouse and grows into a labelled ring over links.

import { BrandImage, CustomCursor } from '@brand-studio/ui';
import { cameraViews } from '@/demos/assets';

const stories = [
  { title: 'Built around one lens', image: cameraViews[1] },
  { title: 'A grip cut from walnut', image: cameraViews[2] },
  { title: 'Three dials, no menus', image: cameraViews[4] },
];

export default function CustomCursorDemo() {
  return (
    <CustomCursor style={{ width: '100%', maxWidth: 720, padding: 8 }}>
      <ul style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 16, margin: 0, padding: 0, listStyle: 'none' }}>
        {stories.map(story => (
          <li key={story.title}>
            <a href="#" data-cursor="Read" style={{ display: 'block', textDecoration: 'none' }}>
              <BrandImage asset={story.image} sizes="240px" className="demo-cursor-image" />
              <span style={{ display: 'block', marginTop: 10, fontWeight: 600 }}>{story.title}</span>
            </a>
          </li>
        ))}
      </ul>
      <style>{'.demo-cursor-image { aspect-ratio: 1; border-radius: 12px; }'}</style>
    </CustomCursor>
  );
}

Installation

pnpm add @brand-studio/ui

Usage

import { CustomCursor } from '@brand-studio/ui';
<CustomCursor>
  <a href="/journal/lens" data-cursor="Read">Built around one lens</a>
</CustomCursor>

The dot appears only inside the wrapper. It grows over any link or button; add data-cursor="Word" to an element to show a word in the ring.

Accessibility

  • The system cursor stays visible; the dot is a decoration hidden from screen readers.
  • Touch screens and reduced motion get no dot.

API Reference

PropTypeDefault

Also accepts every HTMLAttributes<HTMLDivElement> attribute.

Still, Deskhand and Hollis are fictional brands. The coffee photographs are original generated artwork. @brand-studio/ui is MIT licensed.