Vize

Hotspot

Headless image hotspots: positioned markers with popover details and SVG regions.

Package @vizejs/ui/hotspot
Maturity stable
Own the source vize lib pull hotspot
Requires context, controllable-state, id, popover
Aliases hotspot, image map, annotated image, shoppable image, product tags
Covers HTML map/area, WAI-ARIA Disclosure, Popover API

Usage

import { HotspotArea, HotspotContent, HotspotImage, HotspotMarker, Hotspot, HotspotRoot } from "@vizejs/ui/hotspot";

Or copy the source into your project with vize lib pull hotspot (see Source Distribution).

API

HotspotArea

Source: src/families/media/hotspot/hotspot-area.vue

Props

Prop Type Default Description
id (required) string required Area id; activating the area makes it the root's active hotspot.
shape (required) HotspotShape required Region in image-space percent.
label (required) string required Accessible name of the region.
href string undefined Render the region as a link to this URL instead of a toggle button.
disabled boolean false Remove the region from interaction.

Events

Event Payload Description
activate [nativeEvent: Event] Fired when a button region is activated, before the active hotspot toggles.

Slots

Slot Slot props Description
default HotspotAreaSlotState Extra SVG content drawn inside the region, e.g. a <title>.

Exposed

Member Type Description
id string Area id.
active boolean Whether this area is the active hotspot.
element SVGSVGElement | null Rendered SVG overlay.
shape HotspotShape Area shape.

HotspotContent

Source: src/families/media/hotspot/hotspot-content.vue

Props

Prop Type Default Description
placement PopoverPlacement "top" Preferred placement relative to the marker before collision handling.
offset number 8 Gap between marker and content in CSS pixels.
portalDisabled boolean false Render in place instead of portalling to document.body.
closeOnEscape boolean true Close when Escape is pressed while the content or marker has focus.
closeOnPointerDownOutside boolean true Close on pointer down outside the content and marker.
closeOnFocusOutside boolean false Close when focus moves outside. Off by default so neighbouring markers, and focus restored from another marker's content, never close this one.
ariaLabel string undefined Accessible dialog name when no labelled element is referenced.
ariaLabelledby string undefined Space-separated ids that label the content.

Slots

Slot Slot props Description
default HotspotMarkerSlotState Hotspot details. Receives the marker state.

HotspotImage

Source: src/families/media/hotspot/hotspot-image.vue

Props

Prop Type Default Description
src (required) string required Image source. Unsafe or malformed sources are not rendered.
alt (required) string required Alternative text describing the whole image.
width number | string undefined Intrinsic width reserving layout space.
height number | string undefined Intrinsic height reserving layout space.
loading "eager" | "lazy" "lazy" Native loading policy.

Exposed

Member Type Description
element HTMLImageElement | null Rendered native image.

HotspotMarker

Source: src/families/media/hotspot/hotspot-marker.vue

Props

Prop Type Default Description
id (required) string required Marker id, unique within the root.
x (required) number required Horizontal position in percent of the image width.
y (required) number required Vertical position in percent of the image height.
label (required) string required Accessible name of the marker button.
disabled boolean false Remove the marker from interaction while keeping it rendered.

Slots

Slot Slot props Description
trigger HotspotMarkerSlotState Visual marker contents rendered inside the button.
default HotspotMarkerSlotState HotspotContent for this marker.

Exposed

Member Type Description
id string Marker id.
open boolean Whether the marker content is open.
disabled boolean Whether the marker is disabled.
state HotspotMarkerState Stable state token.
element HTMLDivElement | null Rendered marker wrapper.
focus (options?: FocusOptions) => void Focus the marker button.

Hotspot<Data = undefined>

Source: src/families/media/hotspot/hotspot-root.vue

Props

Prop Type Default Description
id string | null undefined Consumer-owned base id. null and undefined select a deterministic fallback.
hotspots readonly HotspotDefinition<Data>[] [] Declared markers. Their data type flows into the default slot so markers can be rendered with v-for while keeping consumer payloads typed.
active string | null undefined Controlled active marker id (v-model:active). undefined selects uncontrolled behavior.
defaultActive string | null null Initially active marker for uncontrolled use.
exclusive boolean true Keep at most one marker open. When false, markers open independently and active tracks the most recently opened one.

Events

Event Payload Description
update:active [id: string | null] Fired when the active marker requests a new controlled value.
activeChange [id: string | null, previous: string | null, source: HotspotActiveChangeSource] Fired after every distinct active-marker change with its source.

Slots

Slot Slot props Description
default HotspotSlotState<Data> Image, markers, areas, and content. Receives the typed hotspot list and open state.

Exposed

Member Type Description
active —
close —
element Readonly<ShallowRef<HTMLElement | null>> Template ref to the rendered root element.
hitTest —
open —
openIds —

HotspotRoot<Data = undefined>

Source: src/families/media/hotspot/hotspot-root.vue

Props

Prop Type Default Description
id string | null undefined Consumer-owned base id. null and undefined select a deterministic fallback.
hotspots readonly HotspotDefinition<Data>[] [] Declared markers. Their data type flows into the default slot so markers can be rendered with v-for while keeping consumer payloads typed.
active string | null undefined Controlled active marker id (v-model:active). undefined selects uncontrolled behavior.
defaultActive string | null null Initially active marker for uncontrolled use.
exclusive boolean true Keep at most one marker open. When false, markers open independently and active tracks the most recently opened one.

Events

Event Payload Description
update:active [id: string | null] Fired when the active marker requests a new controlled value.
activeChange [id: string | null, previous: string | null, source: HotspotActiveChangeSource] Fired after every distinct active-marker change with its source.

Slots

Slot Slot props Description
default HotspotSlotState<Data> Image, markers, areas, and content. Receives the typed hotspot list and open state.

Exposed

Member Type Description
element HTMLDivElement | null Rendered root element.
active string | null Most recently opened marker, or null.
openIds readonly string[] Ids of every open marker.
open (id: string) => boolean Open one marker (closing others unless exclusive is false). Reports whether state changed.
close (id?: string) => boolean Close one marker, or every marker without an id. Reports whether state changed.
hitTest (x: number, y: number) => readonly string[] Ids of registered HotspotAreas whose shape contains an image-space point (percent).

Behavior

Normative state x input -> outcome table for hotspot-root.vue, hotspot-image.vue, hotspot-marker.vue, hotspot-content.vue, and hotspot-area.vue (@vizejs/ui/hotspot). Every row is proven by the named test.

Markers are positioned in image space: each HotspotMarker publishes --vize-ui-hotspot-x / --vize-ui-hotspot-y percentages, so a consumer rule like position: absolute; left: var(--vize-ui-hotspot-x); top: var(--vize-ui-hotspot-y) keeps markers attached to the image at every size. Each marker is a Popover (PopoverRoot + PopoverTrigger button), and HotspotContent wraps PopoverContent, inheriting Escape, outside dismissal, positioning, and focus return. HotspotArea draws a region on a viewBox="0 0 100 100" SVG overlay.

ID State Input Outcome Evidence
H1 default render markers render as named buttons with position variables and states; the image keeps safe src/alt; disabled markers are disabled buttons renders positioned marker buttons, a safe image, regions, and typed slot data
H2 exclusive (default) marker click opens that marker's content, closes the previous one, toggles closed on a second click; emits update:active and activeChange clicking a marker opens its content and exclusive roots close the previous one
H3 open content Escape closes the content and returns focus to its marker Escape closes the open content and returns focus to its marker
H4 exclusive=false clicks / open() / close() several markers stay open; active is the latest; closing it restores the previous one; close() without an id closes all non-exclusive roots keep several markers open and track the latest as active
H5 focused marker Arrow keys focus moves to the nearest enabled marker inside a 90° cone in that direction; modified keys and dead ends pass through arrow keys move focus to the spatially nearest enabled marker
H6 controlled active click / prop change emits the request while the controlled marker stays open until the parent accepts it controlled active wins until the parent accepts the request
H7 regions click / Enter / hit test button regions toggle with aria-pressed; link regions render sanitized href; hitTest() returns areas containing a point regions toggle as buttons, render safe links, and support hit testing
H8 unsafe / disabled render / click unsafe image sources and links are dropped; disabled regions leave the tab order and ignore activation unsafe links and images are not rendered and disabled regions stay inert
H9 region listener activate + preventDefault() the toggle is cancelled region activate listeners can cancel the toggle
H10 missing provider setup markers and areas require HotspotRoot; content requires HotspotMarker compound parts require matching providers
H11 pure geometry helpers even-odd polygon test, inclusive rect/circle tests, point formatting, clamping, directional search, and href sanitizing are deterministic hotspot-geometry.test.ts
H12 SSR isolated requests markup is byte-identical, including generated marker ids, the open default marker, and the SVG region renders byte-identical hotspot markup across isolated SSR requests
H13 SSR / hydration hydrate server markup hydrates without warnings or node replacement hydrates hotspot markup without warnings or node replacement
H14 types compile hotspot payloads flow through HotspotSlotState<Data>; states, shapes, and exposes are closed and read-only hotspot.types.test-d.ts

HotspotContent defaults closeOnFocusOutside to false so that focus returning to one marker never closes another open marker.