Hotspot Click-to-label image annotation — learners type the correct label for each marked point on an image.
npx shadcn@latest add https://lmscn.vercel.app/r/hotspot.json
Required primitives: button, badge, input, progress, popover
npx shadcn@latest add button badge input progress popover
import { Hotspot } from "@/components/lms/hotspot"
import type { HotspotData, HotspotResult } from "@/components/lms/hotspot"
export function MyHotspot () {
return (
< Hotspot
hotspotData = {{
title: "Heart Anatomy" ,
questions: [
{
id: "q1" ,
imageUrl: "/heart-diagram.png" ,
imageAlt: "Diagram of the human heart" ,
prompt: "Label the four chambers of the heart:" ,
caseSensitive: false ,
points: [
{
id: "p1" ,
x: 30 ,
y: 40 ,
label: "Left Ventricle" ,
description: "Pumps oxygenated blood to the body." ,
},
{
id: "p2" ,
x: 60 ,
y: 40 ,
label: "Right Ventricle" ,
description: "Pumps deoxygenated blood to the lungs." ,
},
{ id: "p3" , x: 30 , y: 20 , label: "Left Atrium" },
{ id: "p4" , x: 60 , y: 20 , label: "Right Atrium" },
],
},
],
}}
onComplete = {( result : HotspotResult ) => {
console. log (result.percentage + "% correct" )
}}
/>
)
}
Prop Type Required Description hotspotDataHotspotData✓ Questions and image configuration onComplete(result: HotspotResult) => void— Called after the last question classNamestring— Additional CSS classes
Field Type Description titlestringActivity title descriptionstringSubtitle questionsHotspotQuestion[]List of hotspot questions
Field Type Description idstringUnique identifier imageUrlstringURL of the background image imageAltstringAccessible alt text for the image promptstringInstruction shown above the image pointsHotspotPoint[]Marker definitions caseSensitivebooleanCase-sensitive label matching — defaults to false
Field Type Description idstringUnique identifier xnumberHorizontal position as a percentage from the left edge (0–100) ynumberVertical position as a percentage from the top edge (0–100) labelstringThe correct label the learner must type descriptionstringOptional detail revealed in the popover after a correct answer
onComplete receives a HotspotResult object:
Field Type Description attemptsHotspotAttempt[]Per-question records totalScorenumberTotal correct labels across all questions maxScorenumberTotal labels across all questions percentagenumberRounded score percentage
interface HotspotAttempt {
questionId : string
answers : Record < string , string > // pointId → typed answer
score : number // correct labels in this question
maxScore : number // total labels in this question
}
Each marker is rendered as a circular + button absolutely positioned over the image using the x / y percentage values.
Clicking a marker opens a Popover containing a text input. Pressing Enter closes the popover and saves the typed value.
After the learner clicks Check Labels , all popovers switch to read-only mode showing ✓ / ✗ and the correct answer for incorrect points.
Markers turn green (correct) or red (incorrect) after checking.
The progress bar advances per question, not per label.
The x and y values are CSS percentages relative to the image's rendered size, so they remain accurate regardless of the container width. Use your browser's developer tools to find the right coordinates:
Open the image in a browser.
Right-click → Inspect , then hover over the image element.
Note the pixel dimensions, then calculate x = (pixelX / width) * 100 and y = (pixelY / height) * 100.