Tap Hint

A small pulsing "tap here" badge that points at an interactive element. Solves the mobile touch-affordance gap: on desktop, hover gives feedback that an element is interactive — on touch there is nothing. Drop a TapHint next to a target with the position you want it to sit on, and the pointer rotates so it always aims back at the target.

Preview
top
top
right
right
bottom
bottom
left
left

Installation

npx shadcn@latest add https://craftbits.dev/r/tap-hint.json

Usage

import { TapHint } from "@craft-bits/core";
 
<div className="relative inline-block">
  <button>Add to cart</button>
  <div className="absolute -bottom-9 left-1/2 -translate-x-1/2">
    <TapHint position="bottom" label="tap here" />
  </div>
</div>

Custom label and side:

<TapHint position="right" label="drag me" />

Icon-only (no label):

<TapHint position="top" label="" />

Anatomy

  • Pointer icon — rotates per position so the nib always aims back at the target. The accent color makes it pop against the badge fill.
  • Pill — a soft --cb-bg-elevated background with a hairline --cb-border-muted ring. Carries a role="note" and ignores pointer events.
  • Label — short action text next to the icon (tap, drag me, from right). Pass an empty string to render the icon alone.
  • Idle bob — both icon and label drift 2px in the direction of the target on a 2-second loop, gesturing toward where the user should aim.

Props

PropTypeDefaultDescription
position'top' | 'right' | 'bottom' | 'left''bottom'Which side of the target the hint sits on. Drives pointer rotation and bob direction.
labelstring'tap'Short action label next to the icon. Pass an empty string to render the icon alone.
classNamestringMerged onto the root via cn().

Accessibility

  • Renders as <div role="note"> so assistive tech announces it as supplementary content.
  • The pointer icon is aria-hidden="true" so only the label is read aloud.
  • pointer-events: none on the root — the badge never blocks the underlying target from receiving taps.
  • Bob motion is small (2px) and slow (2s period) so it stays comfortable for motion-sensitive users; suppress at the parent if you need to honour prefers-reduced-motion exactly.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/chrome/TapHint.tsx). The source primitive wrapped its target in a flex column and rendered a hint badge below; it also owned its own auto-dismiss-on-tap state. The craft-bits version is presentational only and exposes a position prop so consumers can place the hint on any side of their target, with the pointer icon rotating to match.