Presence Cursor
Another person's pointer on a shared surface, like a canvas or a document. It shows a colored arrow with a name tag, or with an avatar.
Name tags
Each cursor shows the person's email in their color.
Initials
Pass an Avatar with initials to show it next to the arrow instead of the tag.
Face photos
An Avatar with a photo. The initials show until the photo loads.
Usage
import { PresenceCursor } from "@tulipfarm/sprout/presence-cursor";
import { Avatar, AvatarFallback, AvatarImage } from "@tulipfarm/sprout/avatar";
<div className="relative">
{peers.map(peer => (
<PresenceCursor
key={peer.id}
name={peer.email}
color={peer.color}
x={peer.x}
y={peer.y}
/>
))}
</div>
<PresenceCursor
name="[email protected]"
color="#b55309"
x={120}
y={80}
avatar={
<Avatar size="sm">
<AvatarImage src={jenny.image} alt="" />
<AvatarFallback>JE</AvatarFallback>
</Avatar>
}
/>x and y place the arrow tip, in px, from the top left of the nearest positioned parent. Give that parent position: relative. Your app sends the positions, for example from a realtime channel. The cursor does not track anything by itself.
color fills the arrow, the tag, and the avatar ring. The tag text is white, so pick colors with at least 4.5:1 contrast against white. Passavatar to show it in place of the tag. A small Avatar fits best. Long names cut off with an ellipsis at 16rem.
Accessibility
Each cursor is an image named after the person, so screen readers read the name in both forms. It ignores pointer events, so clicks go through it. It glides to a new spot over 180ms, and jumps when reduced motion is requested. In forced colors mode the arrow uses the system text color.
Theme tokens
The tag uses --sprout-radius-control and --sprout-shadow-control. Motion uses --sprout-motion-normal and --sprout-motion-ease. The color comes from the color prop, stored in --sprout-presence-color.