A layout-only wrapper that pins an arbitrary content element to the outer bounds of a target element. It’s content-agnostic — drop in badges, stamps, status dots, icons, or anything else. Positioning is handled by Floating UI with collision-aware flip and shift, and auto-updates on scroll and resize.
The wrapper hugs its target (display: inline-flex, position: relative) and the content slot is absolutely positioned against it. By default the content’s center sits on the target’s corner (badge convention) — switch to anchor="outside" for flush-outside placement.
A status dot pinned to the bottom-end corner of an avatar-like element.
---
import Attachment from "../Attachment.astro";
---
<Attachment placement="bottom-end" shape="circle">
<div
style="width: 64px; height: 64px; background: #d0d7de; border-radius: 50%;"
>
</div>
<span
slot="content"
style="width: 14px; height: 14px; background: #2ecc71; border: 2px solid white; border-radius: 50%; display: block;"
></span>
</Attachment>The placement prop accepts any Floating UI Placement — top-start, top-end, bottom-start, bottom-end, left, right, and so on.
top-start top-end bottom-start bottom-end ---
import Attachment from "../Attachment.astro";
import type { Placement } from "@pindoba/core-attachment";
const placements: Placement[] = [
"top-start",
"top-end",
"bottom-start",
"bottom-end",
];
---
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
{
placements.map((placement) => (
<div style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;">
<Attachment placement={placement}>
<div style="width: 56px; height: 56px; background: #d0d7de; border-radius: 8px;" />
<span
slot="content"
style="min-width: 18px; height: 18px; padding: 0 6px; background: #e74c3c; color: white; border-radius: 999px; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center;"
>
3
</span>
</Attachment>
<code style="font-size: 11px;">{placement}</code>
</div>
))
}
</div>Switch anchor to "outside" when you want the attachment to sit flush outside the target — common for tooltips, pointer labels, or callouts.
corner outside ---
import Attachment from "../Attachment.astro";
---
<div style="display: flex; gap: 3rem;">
<div
style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;"
>
<Attachment placement="top-end" anchor="corner">
<div
style="width: 64px; height: 64px; background: #d0d7de; border-radius: 8px;"
>
</div>
<span
slot="content"
style="min-width: 20px; height: 20px; padding: 0 6px; background: #e74c3c; color: white; border-radius: 999px; font-size: 12px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center;"
>
9
</span>
</Attachment>
<code style="font-size: 11px;">corner</code>
</div>
<div
style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;"
>
<Attachment placement="top-end" anchor="outside">
<div
style="width: 64px; height: 64px; background: #d0d7de; border-radius: 8px;"
>
</div>
<span
slot="content"
style="min-width: 20px; height: 20px; padding: 0 6px; background: #e74c3c; color: white; border-radius: 999px; font-size: 12px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center;"
>
9
</span>
</Attachment>
<code style="font-size: 11px;">outside</code>
</div>
</div>offset stacks on top of the anchor — positive pushes the content further from the target, negative pulls it closer in.
Body text.
---
import Attachment from "../Attachment.astro";
---
<Attachment placement="top-start" offset={-8}>
<div
style="width: 200px; height: 120px; background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 12px; padding: 1rem;"
>
<strong>Card title</strong>
<p style="margin: 0.5rem 0 0; color: #57606a; font-size: 14px;">
Body text.
</p>
</div>
<span
slot="content"
style="padding: 2px 8px; background: #0969da; color: white; border-radius: 4px; font-size: 11px; font-weight: 700; letter-spacing: 0.05em;"
>
NEW
</span>
</Attachment>Numeric counters, unread dots, and presence indicators — the classic “notification badge” use case. Default anchor="corner" keeps the badge centered on the target’s corner.
---
import Attachment from "../Attachment.astro";
import Badge from "@pindoba/astro-badge";
---
<div style="display: flex; gap: 2.5rem; align-items: center;">
<Attachment placement="top-end">
<div
style="width: 48px; height: 48px; background: #d0d7de; border-radius: 8px;"
>
</div>
<Badge slot="content" size="sm" feedback="danger">3</Badge>
</Attachment>
<Attachment placement="top-end">
<div
style="width: 48px; height: 48px; background: #d0d7de; border-radius: 8px;"
>
</div>
<Badge slot="content" size="sm" feedback="danger">99+</Badge>
</Attachment>
<Attachment placement="top-end" shape="circle">
<div
style="width: 48px; height: 48px; background: #d0d7de; border-radius: 50%;"
>
</div>
<Badge slot="content" size="sm" feedback="danger">New</Badge>
</Attachment>
<Attachment placement="bottom-end" shape="circle">
<div
style="width: 48px; height: 48px; background: #d0d7de; border-radius: 50%;"
>
</div>
<Badge slot="content" size="sm" feedback="success">Online</Badge>
</Attachment>
</div>Marketing stamps like NEW, -30%, or a rotated SOLD overlay. A negative offset pulls the stamp inward so it overlaps the card edge.
Premium audio, noise cancelling.
Handcrafted, full-grain leather.
Rare 1970s film shooter.
---
import Attachment from "../Attachment.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
---
<div style="display: flex; gap: 2.5rem; flex-wrap: wrap;">
<Attachment placement="top-start" offset={-4}>
<div
style="width: 200px; height: 120px; background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 12px; padding: 1rem;"
>
<strong>Wireless Headphones</strong>
<p style="margin: 0.5rem 0 0; color: #57606a; font-size: 13px;">
Premium audio, noise cancelling.
</p>
</div>
<Stamp slot="content" size="xs" emphasis="primary"
><Icon name="lucide:star" /></Stamp
>
</Attachment>
<Attachment placement="top-end" offset={-8}>
<div
style="width: 200px; height: 120px; background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 12px; padding: 1rem;"
>
<strong>Leather Wallet</strong>
<p style="margin: 0.5rem 0 0; color: #57606a; font-size: 13px;">
Handcrafted, full-grain leather.
</p>
</div>
<Stamp slot="content" emphasis="secondary"
><Icon name="lucide:flame" /></Stamp
>
</Attachment>
<Attachment placement="top-end" offset={-8}>
<div
style="width: 200px; height: 120px; background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 12px; padding: 1rem;"
>
<strong>Vintage Camera</strong>
<p style="margin: 0.5rem 0 0; color: #57606a; font-size: 13px;">
Rare 1970s film shooter.
</p>
</div>
<Stamp slot="content" emphasis="muted"><Icon name="lucide:check" /></Stamp>
</Attachment>
</div>| prop | type | default | req | description |
|---|---|---|---|---|
| placement | Placement | "top-end" | Floating UI placement relative to the target's outer bounds. | |
| anchor | "corner""outside" | "corner" | "corner" centers the attachment on the target corner (badges, dots). "outside" places it flush against the target edge (tooltips, popovers). | |
| shape | "rect""circle" | "rect" | Silhouette of the target. Use "circle" so diagonal placements hug the arc of a round target instead of floating off its bounding corner. | |
| offset | number | 0 | Extra px nudge along the placement axis, stacked on top of the anchor. | |
| flip | boolean | true | Flip to the opposite side when overflowing the viewport. | |
| shift | boolean | true | Shift along the alignment axis to stay within the viewport. | |
| padding | number | 0 | Viewport padding used by the flip/shift middleware. | |
| passThrough | { root?, content? } | — | Override slot styles or props. |