<div class="ca"><span class="lbl">Hotspots on a product photo</span>
<figure class="ca-photo">
<img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&fit=crop&w=1200&h=800&q=70" width="1200" height="800" alt="Black over-ear headphones lying on a bright yellow background">
<div class="ca-layer" aria-hidden="true">
<span class="ca-dot" style="--x:66%;--y:17%">1</span>
<span class="ca-dot" style="--x:53%;--y:60%">2</span>
<span class="ca-dot" style="--x:71%;--y:73%">3</span>
</div>
<ol class="ca-layer notes" aria-label="Product details">
<li class="ca-pin" style="--x:66%;--y:17%"><p class="ca-tip ca-l"><span class="ca-num" aria-hidden="true">1</span>Padded headband. 248 g in total.</p></li>
<li class="ca-pin" style="--x:53%;--y:60%"><p class="ca-tip ca-l"><span class="ca-num" aria-hidden="true">2</span>Memory-foam cushions that swap out in seconds.</p></li>
<li class="ca-pin" style="--x:71%;--y:73%"><p class="ca-tip ca-b"><span class="ca-num" aria-hidden="true">3</span>Power, pairing and USB‑C on the right cup.</p></li>
</ol>
<figcaption>Tessel Studio 2 wireless headphones, 32 hours per charge.</figcaption>
</figure></div>
<div class="ca"><span class="lbl">Callout pointing at a UI element</span>
<div class="ca-app">
<div class="ca-bar" role="group" aria-label="Board tools">
<button class="ca-btn" type="button" aria-label="Bold">B</button>
<button class="ca-btn" type="button" aria-label="Italic"><i>I</i></button>
<button class="ca-btn" type="button" aria-label="Add link"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1"/></svg></button>
<span class="ca-sep" aria-hidden="true"></span>
<span class="ca-anchor">
<button class="ca-btn export" type="button" aria-describedby="ca-new"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 15V4M7.5 8.5 12 4l4.5 4.5M5 14v5h14v-5"/></svg>Export</button>
<div class="ca-tip ca-pop" id="ca-new" role="note">
<strong>New: export to PDF</strong>
<p>Send the whole board as a print-ready PDF, with or without comments.</p>
<a href="#">See how it works</a>
</div>
</span>
</div>
<div class="ca-doc" aria-hidden="true"><i></i><i></i><i></i></div>
</div></div>
<div class="ca"><span class="lbl">Annotations on a chart</span>
<figure class="ca-chart">
<figcaption>Orders per month, 2025</figcaption>
<div class="ca-plot" role="img" aria-label="Bar chart of orders per month in 2025. Lowest: February, 410 orders, during a two-day site outage. Highest: November, 1,920 orders, in Black Friday week.">
<span class="ca-col" style="--h:34%"></span>
<span class="ca-col lo" style="--h:21%"><span class="ca-pin"><span class="ca-tip ca-t start">Site outage, 2 days: 410</span></span></span>
<span class="ca-col" style="--h:40%"></span>
<span class="ca-col" style="--h:44%"></span>
<span class="ca-col" style="--h:49%"></span>
<span class="ca-col" style="--h:46%"></span>
<span class="ca-col" style="--h:42%"></span>
<span class="ca-col" style="--h:47%"></span>
<span class="ca-col" style="--h:55%"></span>
<span class="ca-col" style="--h:63%"></span>
<span class="ca-col hi" style="--h:100%"><span class="ca-pin"><span class="ca-tip ca-l">Black Friday week: 1,920</span></span></span>
<span class="ca-col" style="--h:72%"></span>
</div>
<ol class="ca-months" aria-hidden="true"><li>Jan</li><li>Feb</li><li>Mar</li><li>Apr</li><li>May</li><li>Jun</li><li>Jul</li><li>Aug</li><li>Sep</li><li>Oct</li><li>Nov</li><li>Dec</li></ol>
</figure></div>
CSS
/* Callouts and annotation bubbles. A .ca-pin is a zero-size point placed with --x and --y; the bubble is positioned
from that point and its border-triangle tail always lands on it. .ca-l / .ca-r / .ca-t / .ca-b say where the bubble sits. */
.ca{--ca-bg:#18213a;--ca-ink:#fff;--ca-accent:#e11d48;--ca-t:8px;--ca-gap:25px;--ca-muted:#565d70}
.ca .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.ca-tip{position:absolute;z-index:1;width:max-content;max-width:210px;margin:0;padding:9px 13px;border-radius:10px;background:var(--ca-bg);color:var(--ca-ink);
font-size:.84rem;line-height:1.4;box-shadow:0 10px 24px -10px rgba(24,33,58,.55)}
.ca-tip::after{content:"";position:absolute;border:var(--ca-t) solid transparent}
.ca-l{right:var(--ca-gap);top:0;translate:0 -50%}
.ca-l::after{left:100%;top:50%;margin-top:calc(var(--ca-t) * -1);border-right-width:0;border-left-color:var(--ca-bg)}
.ca-r{left:var(--ca-gap);top:0;translate:0 -50%}
.ca-r::after{right:100%;top:50%;margin-top:calc(var(--ca-t) * -1);border-left-width:0;border-right-color:var(--ca-bg)}
.ca-b{top:var(--ca-gap);left:0;translate:-50% 0}
.ca-b::after{bottom:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-top-width:0;border-bottom-color:var(--ca-bg)}
.ca-t{bottom:var(--ca-gap);left:0;translate:-50% 0}
.ca-t::after{top:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-bottom-width:0;border-top-color:var(--ca-bg)}
.ca-num{display:inline-grid;place-items:center;width:18px;height:18px;margin-right:6px;border-radius:50%;background:var(--ca-accent);color:#fff;font-size:.7rem;font-weight:700;vertical-align:1px}
/* 1. Photo hotspots: numbered dots over the image, notes in a list laid over the same box */
.ca-photo{position:relative;margin:0}
.ca-photo img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;border-radius:16px}
.ca-layer{position:absolute;top:0;left:0;width:100%;aspect-ratio:3/2;margin:0;padding:0;list-style:none}
.ca-pin{position:absolute;left:var(--x);top:var(--y);width:0;height:0}
.ca-dot{position:absolute;left:var(--x);top:var(--y);display:grid;place-items:center;width:26px;height:26px;translate:-50% -50%;border-radius:50%;
background:var(--ca-accent);color:#fff;font-size:.76rem;font-weight:700;box-shadow:0 0 0 4px rgba(255,255,255,.75),0 4px 10px rgba(24,33,58,.4)}
.ca-photo figcaption{margin-top:12px;font-size:.82rem;color:var(--ca-muted)}
/* Phones: the notes leave the photo and become a numbered list under it */
@media (max-width:640px){
.ca-photo .ca-layer.notes{position:static;aspect-ratio:auto;display:grid;gap:8px;margin-top:14px}
.ca-photo .ca-pin{position:static;width:auto;height:auto}
.ca-photo .ca-tip{position:static;translate:none;width:auto;max-width:none}
.ca-photo .ca-tip::after{display:none}
}
/* 2. Callout on a UI element: the bubble hangs under the button it introduces */
.ca-app{border:1px solid #dfe2ec;border-radius:16px;background:#fff;box-shadow:0 14px 34px -24px rgba(24,33,58,.45)}
.ca-bar{display:flex;align-items:center;gap:4px;padding:10px 12px;border-bottom:1px solid #e7e9f1}
.ca-btn{display:inline-grid;place-items:center;min-width:36px;height:36px;padding:0 8px;border:0;border-radius:8px;background:none;color:#2d3550;font:inherit;font-size:.9rem;font-weight:700;cursor:pointer}
.ca-btn:hover{background:#f0f2f8}
.ca-sep{width:1px;height:22px;margin:0 6px;background:#dfe2ec}
.ca-anchor{position:relative;margin-left:auto}
.ca-btn.export{display:inline-flex;gap:6px;padding:0 14px;background:#ede9fe;color:#4c1d95}
.ca-btn.export:hover{background:#ddd6fe}
.ca-pop{--ca-bg:#4c1d95;top:calc(100% + 14px);right:0;width:250px;max-width:calc(100vw - 64px);padding:14px 16px;font-size:.86rem}
.ca-pop::after{bottom:100%;right:38px;border-top-width:0;border-bottom-color:var(--ca-bg)}
.ca-pop strong{display:block;margin-bottom:3px;font-size:.92rem}
.ca-pop p{margin:0 0 8px}
.ca-pop a{color:#fff;font-weight:600;text-underline-offset:3px}
.ca-doc{display:grid;gap:12px;padding:150px 24px 28px}
.ca-doc i{display:block;height:10px;border-radius:5px;background:#eceef5}
.ca-doc i:nth-child(2){width:86%}.ca-doc i:nth-child(3){width:64%}
.ca-btn:focus-visible{outline:2px solid #4c1d95;outline-offset:2px}
.ca-pop a:focus-visible{outline:2px solid #fff;outline-offset:2px;border-radius:2px}
/* 3. Chart annotation: bubbles pinned to the top of a bar */
.ca-chart{margin:0;padding:22px 22px 16px;border:1px solid #dfe2ec;border-radius:16px;background:#fff}
.ca-chart figcaption{margin-bottom:18px;font-size:.9rem;font-weight:700}
.ca-plot{display:flex;align-items:flex-end;gap:6px;height:220px;padding-top:70px;border-bottom:1px solid #cfd3df}
.ca-col{position:relative;flex:1;height:var(--h);border-radius:6px 6px 0 0;background:#c7cbe0}
.ca-col.hi{background:var(--ca-accent)}
.ca-col.lo{background:#7c86a8}
.ca-col .ca-pin{left:50%;top:0}
.ca-months{display:flex;gap:6px;margin:6px 0 0;padding:0;list-style:none}
.ca-months li{flex:1;text-align:center;font-size:.7rem;color:var(--ca-muted)}
.ca-chart .ca-tip{max-width:170px;font-size:.78rem}
.ca-t.start{translate:-20px 0}
.ca-t.start::after{left:20px}
@media (max-width:640px){.ca-plot,.ca-months{gap:3px}.ca-months li{font-size:0}.ca-months li::first-letter{font-size:.7rem}.ca-chart{padding:18px 14px 14px}.ca-chart .ca-tip{max-width:150px}}
Bootstrap 5 code
Requires: Bootstrap 5.3.8 CSS
HTML
<div class="ca"><span class="lbl">Hotspots on a product photo</span>
<figure class="ca-photo figure d-block position-relative mb-0">
<img class="figure-img img-fluid rounded-4 mb-0" src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&fit=crop&w=1200&h=800&q=70" width="1200" height="800" alt="Black over-ear headphones lying on a bright yellow background">
<div class="ca-layer" aria-hidden="true">
<span class="ca-dot" style="--x:66%;--y:17%">1</span>
<span class="ca-dot" style="--x:53%;--y:60%">2</span>
<span class="ca-dot" style="--x:71%;--y:73%">3</span>
</div>
<ol class="ca-layer notes list-unstyled" aria-label="Product details">
<li class="ca-pin" style="--x:66%;--y:17%"><p class="ca-tip ca-l"><span class="ca-num" aria-hidden="true">1</span>Padded headband. 248 g in total.</p></li>
<li class="ca-pin" style="--x:53%;--y:60%"><p class="ca-tip ca-l"><span class="ca-num" aria-hidden="true">2</span>Memory-foam cushions that swap out in seconds.</p></li>
<li class="ca-pin" style="--x:71%;--y:73%"><p class="ca-tip ca-b"><span class="ca-num" aria-hidden="true">3</span>Power, pairing and USB‑C on the right cup.</p></li>
</ol>
<figcaption class="figure-caption mt-2">Tessel Studio 2 wireless headphones, 32 hours per charge.</figcaption>
</figure></div>
<div class="ca"><span class="lbl">Callout pointing at a UI element</span>
<div class="ca-app card">
<div class="ca-bar card-header bg-transparent d-flex align-items-center gap-1" role="group" aria-label="Board tools">
<button class="btn ca-btn" type="button" aria-label="Bold">B</button>
<button class="btn ca-btn" type="button" aria-label="Italic"><i>I</i></button>
<button class="btn ca-btn" type="button" aria-label="Add link"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1"/></svg></button>
<span class="ca-sep" aria-hidden="true"></span>
<span class="ca-anchor position-relative ms-auto">
<button class="btn ca-btn export" type="button" aria-describedby="ca-new"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 15V4M7.5 8.5 12 4l4.5 4.5M5 14v5h14v-5"/></svg>Export</button>
<div class="ca-tip ca-pop" id="ca-new" role="note">
<strong class="d-block mb-1">New: export to PDF</strong>
<p>Send the whole board as a print-ready PDF, with or without comments.</p>
<a class="link-light link-offset-1 fw-semibold" href="#">See how it works</a>
</div>
</span>
</div>
<div class="ca-doc" aria-hidden="true"><i></i><i></i><i></i></div>
</div></div>
<div class="ca"><span class="lbl">Annotations on a chart</span>
<figure class="ca-chart card card-body mb-0">
<figcaption class="fw-bold mb-3">Orders per month, 2025</figcaption>
<div class="ca-plot" role="img" aria-label="Bar chart of orders per month in 2025. Lowest: February, 410 orders, during a two-day site outage. Highest: November, 1,920 orders, in Black Friday week.">
<span class="ca-col" style="--h:34%"></span>
<span class="ca-col lo" style="--h:21%"><span class="ca-pin"><span class="ca-tip ca-t start">Site outage, 2 days: 410</span></span></span>
<span class="ca-col" style="--h:40%"></span>
<span class="ca-col" style="--h:44%"></span>
<span class="ca-col" style="--h:49%"></span>
<span class="ca-col" style="--h:46%"></span>
<span class="ca-col" style="--h:42%"></span>
<span class="ca-col" style="--h:47%"></span>
<span class="ca-col" style="--h:55%"></span>
<span class="ca-col" style="--h:63%"></span>
<span class="ca-col hi" style="--h:100%"><span class="ca-pin"><span class="ca-tip ca-l">Black Friday week: 1,920</span></span></span>
<span class="ca-col" style="--h:72%"></span>
</div>
<ol class="ca-months list-unstyled" aria-hidden="true"><li>Jan</li><li>Feb</li><li>Mar</li><li>Apr</li><li>May</li><li>Jun</li><li>Jul</li><li>Aug</li><li>Sep</li><li>Oct</li><li>Nov</li><li>Dec</li></ol>
</figure></div>
CSS
/* Callouts and annotation bubbles on Bootstrap (.figure, .card, .btn and utilities around them). A .ca-pin is a zero-size point placed with --x and --y; the bubble is positioned
from that point and its border-triangle tail always lands on it. .ca-l / .ca-r / .ca-t / .ca-b say where the bubble sits. */
.ca{--ca-bg:#18213a;--ca-ink:#fff;--ca-accent:#e11d48;--ca-t:8px;--ca-gap:25px;--ca-muted:#565d70}
.ca-tip{position:absolute;z-index:1;width:max-content;max-width:210px;margin:0;padding:9px 13px;border-radius:10px;background:var(--ca-bg);color:var(--ca-ink);
font-size:.84rem;line-height:1.4;box-shadow:0 10px 24px -10px rgba(24,33,58,.55)}
.ca-tip::after{content:"";position:absolute;border:var(--ca-t) solid transparent}
.ca-l{right:var(--ca-gap);top:0;translate:0 -50%}
.ca-l::after{left:100%;top:50%;margin-top:calc(var(--ca-t) * -1);border-right-width:0;border-left-color:var(--ca-bg)}
.ca-r{left:var(--ca-gap);top:0;translate:0 -50%}
.ca-r::after{right:100%;top:50%;margin-top:calc(var(--ca-t) * -1);border-left-width:0;border-right-color:var(--ca-bg)}
.ca-b{top:var(--ca-gap);left:0;translate:-50% 0}
.ca-b::after{bottom:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-top-width:0;border-bottom-color:var(--ca-bg)}
.ca-t{bottom:var(--ca-gap);left:0;translate:-50% 0}
.ca-t::after{top:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-bottom-width:0;border-top-color:var(--ca-bg)}
.ca-num{display:inline-grid;place-items:center;width:18px;height:18px;margin-right:6px;border-radius:50%;background:var(--ca-accent);color:#fff;font-size:.7rem;font-weight:700;vertical-align:1px}
/* 1. Photo hotspots: numbered dots over the image, notes in a list laid over the same box */
.ca-photo{position:relative;margin:0}
.ca-photo img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;border-radius:16px}
.ca-layer{position:absolute;top:0;left:0;width:100%;aspect-ratio:3/2;margin:0;padding:0;list-style:none}
.ca-pin{position:absolute;left:var(--x);top:var(--y);width:0;height:0}
.ca-dot{position:absolute;left:var(--x);top:var(--y);display:grid;place-items:center;width:26px;height:26px;translate:-50% -50%;border-radius:50%;
background:var(--ca-accent);color:#fff;font-size:.76rem;font-weight:700;box-shadow:0 0 0 4px rgba(255,255,255,.75),0 4px 10px rgba(24,33,58,.4)}
.ca-photo figcaption{margin-top:12px;font-size:.82rem;color:var(--ca-muted)}
/* Phones: the notes leave the photo and become a numbered list under it */
@media (max-width:640px){
.ca-photo .ca-layer.notes{position:static;aspect-ratio:auto;display:grid;gap:8px;margin-top:14px}
.ca-photo .ca-pin{position:static;width:auto;height:auto}
.ca-photo .ca-tip{position:static;translate:none;width:auto;max-width:none}
.ca-photo .ca-tip::after{display:none}
}
/* 2. Callout on a UI element: the bubble hangs under the button it introduces */
.ca-app{--bs-card-border-color:#dfe2ec;--bs-card-border-radius:16px;--bs-card-cap-padding-y:10px;--bs-card-cap-padding-x:12px;box-shadow:0 14px 34px -24px rgba(24,33,58,.45)}
.ca-bar{display:flex;align-items:center;gap:4px;padding:10px 12px;border-bottom:1px solid #e7e9f1}
.ca-btn{display:inline-grid;place-items:center;min-width:36px;height:36px;padding:0 8px;border:0;border-radius:8px;background:none;color:#2d3550;font:inherit;font-size:.9rem;font-weight:700;cursor:pointer}
.ca-btn:hover{background:#f0f2f8}
.ca-sep{width:1px;height:22px;margin:0 6px;background:#dfe2ec}
.ca-anchor{position:relative;margin-left:auto}
.ca-btn.export{display:inline-flex;gap:6px;padding:0 14px;background:#ede9fe;color:#4c1d95}
.ca-btn.export:hover{background:#ddd6fe}
.ca-pop{--ca-bg:#4c1d95;top:calc(100% + 14px);right:0;width:250px;max-width:calc(100vw - 64px);padding:14px 16px;font-size:.86rem}
.ca-pop::after{bottom:100%;right:38px;border-top-width:0;border-bottom-color:var(--ca-bg)}
.ca-pop strong{display:block;margin-bottom:3px;font-size:.92rem}
.ca-pop p{margin:0 0 8px}
.ca-doc{display:grid;gap:12px;padding:150px 24px 28px}
.ca-doc i{display:block;height:10px;border-radius:5px;background:#eceef5}
.ca-doc i:nth-child(2){width:86%}.ca-doc i:nth-child(3){width:64%}
.ca-btn:focus-visible{outline:2px solid #4c1d95;outline-offset:2px}
.ca-pop a:focus-visible{outline:2px solid #fff;outline-offset:2px;border-radius:2px}
/* 3. Chart annotation: bubbles pinned to the top of a bar */
.ca-chart{--bs-card-border-color:#dfe2ec;--bs-card-border-radius:16px;padding:22px 22px 16px}
.ca-chart figcaption{margin-bottom:18px;font-size:.9rem;font-weight:700}
.ca-plot{display:flex;align-items:flex-end;gap:6px;height:220px;padding-top:70px;border-bottom:1px solid #cfd3df}
.ca-col{position:relative;flex:1;height:var(--h);border-radius:6px 6px 0 0;background:#c7cbe0}
.ca-col.hi{background:var(--ca-accent)}
.ca-col.lo{background:#7c86a8}
.ca-col .ca-pin{left:50%;top:0}
.ca-months{display:flex;gap:6px;margin:6px 0 0;padding:0;list-style:none}
.ca-months li{flex:1;text-align:center;font-size:.7rem;color:var(--ca-muted)}
.ca-chart .ca-tip{max-width:170px;font-size:.78rem}
.ca-t.start{translate:-20px 0}
.ca-t.start::after{left:20px}
@media (max-width:640px){.ca-plot,.ca-months{gap:3px}.ca-months li{font-size:0}.ca-months li::first-letter{font-size:.7rem}.ca-chart{padding:18px 14px 14px}.ca-chart .ca-tip{max-width:150px}}
@theme {
--color-ca-ink:#18213a; --color-ca-accent:#e11d48; --color-ca-muted:#565d70;
}
/* Utilities draw the bubbles; placement from the pin and the tails are repeated rules, so they live here.
A .ca-pin is a zero-size point; .ca-l / .ca-r / .ca-t / .ca-b put the bubble on that side with its tail on the point. */
:root{--ca-bg:var(--color-ca-ink);--ca-t:8px;--ca-gap:25px}
.ca-tip{position:absolute}
.ca-tip::after{content:"";position:absolute;border:var(--ca-t) solid transparent}
.ca-l{right:var(--ca-gap);top:0;translate:0 -50%}
.ca-l::after{left:100%;top:50%;margin-top:calc(var(--ca-t) * -1);border-right-width:0;border-left-color:var(--ca-bg)}
.ca-b{top:var(--ca-gap);left:0;translate:-50% 0}
.ca-b::after{bottom:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-top-width:0;border-bottom-color:var(--ca-bg)}
.ca-t{bottom:var(--ca-gap);left:0;translate:-50% 0}
.ca-t::after{top:100%;left:50%;margin-left:calc(var(--ca-t) * -1);border-bottom-width:0;border-top-color:var(--ca-bg)}
.ca-t.ca-start{translate:-20px 0}
.ca-t.ca-start::after{left:20px}
.ca-pop::after{bottom:100%;right:38px;border-top-width:0;border-bottom-color:var(--ca-bg)}
About this set
Three kinds of bubble that point at something specific. Hotspots on a product photo puts numbered dots on a picture of headphones and a note beside each one whose tail lands on its dot. Each note is a zero-size pin placed with –x and –y in percent, and a class such as .ca-l or .ca-b says which side of the pin the bubble sits on, so the tail always ends on the point however long the text is. Below 640px the notes leave the photo and become a numbered list under it, because notes laid over a phone-sized photo would hide it. Callout pointing at a UI element hangs a new-feature bubble under a toolbar’s Export button, with a heading, one sentence and a link; the button points to it with aria-describedby. Annotations on a chart pins two bubbles to the tops of bars in a monthly orders chart, one beside the November peak and one above the February dip, and the same facts are in the chart’s text alternative. Every bubble uses the same border-triangle tail, so one block of CSS covers all three.
What’s included
Pins placed with --x and --y, with the tail always on the point
Notes become a numbered list under the photo on phones
New-feature callout linked to its button with aria-describedby
Chart annotations pinned to the top of a bar
One tail system for all bubbles: .ca-l, .ca-r, .ca-t, .ca-b
Accessibility
The photo notes are an ordered list named Product details, with the dot numbers repeated visibly in each note and the dots themselves aria-hidden. The toolbar is a labelled group whose buttons have accessible names, and the Export button is described by the callout. The chart is one image with a text alternative that includes both annotations, and buttons and the link show a 2px focus outline.
Customise it
Set --ca-bg, --ca-accent and --ca-t on .ca for bubble colour, dot colour and tail size, and --ca-gap for the distance from the pin. Move a hotspot by changing --x and --y on both its dot and its note. In Tailwind edit the --color-ca-* theme colours and the [--x:…] [--y:…] utilities; Bootstrap uses the same variables with .figure and .card.