lmscn

Hotspot

Click-to-label image annotation — learners type the correct label for each marked point on an image.

Installation

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

Usage

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")
      }}
    />
  )
}

Props

HotspotProps

PropTypeRequiredDescription
hotspotDataHotspotDataQuestions and image configuration
onComplete(result: HotspotResult) => voidCalled after the last question
classNamestringAdditional CSS classes

Data shape

HotspotData

FieldTypeDescription
titlestringActivity title
descriptionstringSubtitle
questionsHotspotQuestion[]List of hotspot questions

HotspotQuestion

FieldTypeDescription
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

HotspotPoint

FieldTypeDescription
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

Result

onComplete receives a HotspotResult object:

FieldTypeDescription
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
}

Behaviour

  • 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.

Positioning tips

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:

  1. Open the image in a browser.
  2. Right-click → Inspect, then hover over the image element.
  3. Note the pixel dimensions, then calculate x = (pixelX / width) * 100 and y = (pixelY / height) * 100.

On this page