Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UI ARC Radiant Components
v4.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

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.

Components Image Hotspots
content interactive
<arc-image-hotspots>

Overview

Image Hotspots lays glowing 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 too, not just 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

Switch between projects without losing your place — every workspace keeps its own layout. Metrics stream in over WebSockets and render at 60fps, no refresh required. Send any panel to CSV, PNG, or a shared link with one click.

Usage

Image Hotspots is in the marketing domain group, so it is deliberately 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.

label string ''
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.
x number 50
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.
y number 50
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.
open boolean false
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-close first — so a listener that calls preventDefault() 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-open detail: {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-close detail: {value: string | number}
Fired before the popover closes and cancelable — preventDefault() vetoes the close. detail.value matches arc-open.

See Also