Image Hotspots
An annotated image with glowing pin markers, each opening a small popover of detail content. Built for product-feature callouts, annotated screenshots, and simple maps.
<arc-image-hotspots>Overview
Image Hotspots lays pins over a picture and gives each pin a popover. Slot the image and the <arc-hotspot> children together. Every pin positions itself from its own x and y attributes, given as percentages of the image, so source order never matters and the markup stays a flat list of facts about the picture.
Each pin is a real button: keyboard-focusable, labeled for screen readers, and carrying aria-expanded popover semantics. Clicking or activating a pin opens its popover anchored above the pin, flipping to fit near viewport edges. The parent keeps one popover open at a time, and an open popover closes on Escape, on a click anywhere else, or on a second click of its pin.
Every close goes through one method, and it can be vetoed. A hotspot's close() fires the cancelable arc-close before it does anything, so a listener calling preventDefault() keeps the popover open, whoever asked for the close. That includes the parent: enforcing one-open-at-a-time is itself a close() call, so a hotspot holding an unsaved note can refuse to be closed by the pin you just clicked, not only by the user pressing Escape. Call it yourself to dismiss a popover from script; the optional first argument, restoreFocus, defaults to true and returns focus to the pin that opened it. Pass false when you are closing one popover in order to open another, so focus lands on the new pin instead of bouncing back.
The pins render server-side at their coordinates because positioning is pure CSS derived from attributes; only the popover interaction needs JavaScript. Every hotspot reports its activity through arc-open and arc-close events that bubble to the parent, with detail.value carrying the hotspot's label, or its index when no label is set.
Guidelines
When to use
- Give every hotspot a label. It names the pin for screen readers, heads the popover, and identifies the hotspot in events
- Keep popover content to a sentence or two; link out for anything longer
- Place pins on the feature they describe, not beside it. Coordinates are the whole message
- Use a handful of pins per image; three to six is the comfortable range
- Constrain the component to a readable width so pin targets stay comfortably apart
- Cancel `arc-close` while a popover holds unsaved input. The veto applies to the parent’s auto-close as well as to Escape
- Pass `close(false)` when your own code closes one popover in order to open another, so focus follows the user forward
When not to use
- Do not crowd pins so close together that their popovers cover each other's targets
- Do not put critical information only in a popover. Undiscovered pins go unread
- Do not use Image Hotspots for step-by-step onboarding. Pins are a flat set with no order, and nothing here sequences them (`arc-guided-tour` was cut in v4; `arc-tour` is the planned rebuild)
- Do not rely on pixel positions in your head; x and y are percentages of the image box
Features
- Percentage-based pin coordinates: responsive by construction, no measuring
- Pulsing accent pins with a glow that rises on hover and focus
- One popover open at a time, coordinated by the parent
- Escape and outside-click both dismiss the open popover
- Popovers flip and shift to stay inside the viewport
- Pins are real buttons: focusable, labeled, `aria-expanded` state
- `arc-open` / `arc-close` events with `detail.value` naming the hotspot
- `arc-close` is cancelable: `preventDefault()` vetoes the close, including the parent’s one-at-a-time enforcement
- `close(restoreFocus)` dismisses a popover from script, with focus return under your control
- Pins server-render at their positions; popovers stay closed without JS
- Ambient pulse is suppressed under `prefers-reduced-motion`
Preview
Usage
Image Hotspots is in the marketing domain group, so it is absent from the default @arclux/arc-ui barrel. Import it from@arclux/arc-ui/marketing, or from its own subpath@arclux/arc-ui/image-hotspots. Everything else (the element, the CSS, the framework wrappers, the support it gets) is unchanged.
This component requires JavaScript. No pure HTML/CSS version is available. Use the Web Component directly or a framework wrapper.
<arc-image-hotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<arc-hotspot x="18" y="30" label="Navigation">
Jump between reports, alerts, and settings from one rail.
</arc-hotspot>
<arc-hotspot x="60" y="45" label="Live charts">
Metrics stream in real time — no refresh required.
</arc-hotspot>
<arc-hotspot x="85" y="78" label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</arc-hotspot>
</arc-image-hotspots>import { ImageHotspots, Hotspot } from '@arclux/arc-ui-react';
export default function FeatureTour() {
return (
<ImageHotspots onArcOpen={(e) => console.log('opened', e.detail.value)}>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<Hotspot x={18} y={30} label="Navigation">
Jump between reports, alerts, and settings from one rail.
</Hotspot>
<Hotspot x={60} y={45} label="Live charts">
Metrics stream in real time — no refresh required.
</Hotspot>
<Hotspot x={85} y={78} label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</Hotspot>
</ImageHotspots>
);
}<script setup>
import { ImageHotspots, Hotspot } from '@arclux/arc-ui-vue';
</script>
<template>
<ImageHotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<Hotspot :x="18" :y="30" label="Navigation">
Jump between reports, alerts, and settings from one rail.
</Hotspot>
<Hotspot :x="60" :y="45" label="Live charts">
Metrics stream in real time — no refresh required.
</Hotspot>
<Hotspot :x="85" :y="78" label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</Hotspot>
</ImageHotspots>
</template><script>
import { ImageHotspots, Hotspot } from '@arclux/arc-ui-svelte';
</script>
<ImageHotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<Hotspot x={18} y={30} label="Navigation">
Jump between reports, alerts, and settings from one rail.
</Hotspot>
<Hotspot x={60} y={45} label="Live charts">
Metrics stream in real time — no refresh required.
</Hotspot>
<Hotspot x={85} y={78} label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</Hotspot>
</ImageHotspots>import { Component } from '@angular/core';
import { ImageHotspots, Hotspot } from '@arclux/arc-ui-angular';
@Component({
imports: [ImageHotspots, Hotspot],
template: `
<arc-image-hotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<arc-hotspot x="18" y="30" label="Navigation">
Jump between reports, alerts, and settings from one rail.
</arc-hotspot>
<arc-hotspot x="60" y="45" label="Live charts">
Metrics stream in real time — no refresh required.
</arc-hotspot>
<arc-hotspot x="85" y="78" label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</arc-hotspot>
</arc-image-hotspots>
`,
})
export class FeatureTourComponent {}import { ImageHotspots, Hotspot } from '@arclux/arc-ui-solid';
export default function FeatureTour() {
return (
<ImageHotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<Hotspot x={18} y={30} label="Navigation">
Jump between reports, alerts, and settings from one rail.
</Hotspot>
<Hotspot x={60} y={45} label="Live charts">
Metrics stream in real time — no refresh required.
</Hotspot>
<Hotspot x={85} y={78} label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</Hotspot>
</ImageHotspots>
);
}import { ImageHotspots, Hotspot } from '@arclux/arc-ui-preact';
export default function FeatureTour() {
return (
<ImageHotspots>
<img src="/screenshots/dashboard.png" alt="Analytics dashboard" />
<Hotspot x={18} y={30} label="Navigation">
Jump between reports, alerts, and settings from one rail.
</Hotspot>
<Hotspot x={60} y={45} label="Live charts">
Metrics stream in real time — no refresh required.
</Hotspot>
<Hotspot x={85} y={78} label="Exports">
Any panel exports to CSV, PNG, or a shared link.
</Hotspot>
</ImageHotspots>
);
}Hotspot
<arc-hotspot>A single glowing pin inside an Image Hotspots surface. The x and y attributes position it as percentages of the image, the label names it, and slotted children become the popover body.
labelstring''- Accessible name for the pin button, repeated as the heading of the popover. Always set it: without a label the pin announces nothing useful to a screen reader.
xnumber50- Horizontal position of the pin as a percentage of the image width, from 0 (left edge) to 100 (right edge). Values outside the range are clamped; a non-numeric value falls back to 50.
ynumber50- Vertical position of the pin as a percentage of the image height, from 0 (top edge) to 100 (bottom edge). Values outside the range are clamped; a non-numeric value falls back to 50.
openbooleanfalse- Whether the pin's popover is currently visible. Reflected as an attribute. Opens on click; closes on Escape, outside click, or a second click on the pin.
Methods
close(restoreFocus?)restoreFocus?: boolean- Close the popover, firing the cancelable
arc-closefirst, so a listener that callspreventDefault()keeps it open, whoever asked for the close. arc-image-hotspots calls this to enforce one-open-at-a-time, which is why a consumer's veto is honoured there too and not only on a manual close.
Events
arc-opendetail:{value: string | number}- Fired when the popover opens. detail.value carries the label, or the pin's index within its parent when no label is set.
arc-closedetail:{value: string | number}- Fired before the popover closes and cancelable: preventDefault() vetoes the close. detail.value matches arc-open.
See Also
- PopoverFloating content panel anchored to a trigger element, with four placement positions and automatic outside-click dismissal.
- TooltipContextual hint that appears on hover or focus, providing supplementary information without cluttering the UI. Supports four placement positions and a configurable show delay.
- ImageEnhanced image component with shimmer loading skeleton, smooth fade-in transition, error fallback, and aspect ratio presets.
- Hotspot