The slot box for addon content inside sized control rows — wrap an icon, Badge, Stamp, or Kbd hint in <Affix> and place it as a child of a control like Button. The parent row publishes its per-size geometry as CSS variables (the Control Var Contract), so row-aware addons size themselves to the row automatically, and the control’s edge padding tightens on whichever side an affix is present — no slot props, no per-component plumbing.
Wrap icons in a
<Stamp>, not a bare<svg>. A Stamp lays the glyph out in a deterministic, row-sized square so it sits identically across every control and size; a bare icon is sized inconsistently (or not at all) depending on the host. Use<Stamp emphasis="ghost">for a plain icon — a centered box with no border or background — and reach for a fill only when the affordance is deliberately accented. Badges, Kbd hints, and other addons already own their box and don’t need wrapping.
Place an <Affix> before the label for a leading addon, or after it with side="end" for a trailing one. A control can carry both at once; the row’s gap and edge padding adjust structurally. Plain icons go in a <Stamp emphasis="ghost">; a <Badge> is already row-aware, so it drops straight in.
---
// GENERATED from packages/demos/affix/basic.demo.tsx — edit the source, not this file.
import Affix from "@pindoba/astro-affix";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Download } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Button size="md">
<Affix>
<Stamp emphasis="ghost">
<Download />
</Stamp>
</Affix>{" "}
Download
</Button>
<Button size="md" emphasis="secondary">
Inbox{" "}
<Affix side="end">
<Badge>12</Badge>
</Affix>
</Button>
<Button size="md">
<Affix>
<Stamp emphasis="ghost">
<Download />
</Stamp>
</Affix>{" "}
Download{" "}
<Affix side="end">
<Badge>2.3 MB</Badge>
</Affix>
</Button>
</div>The side prop picks the edge: start (default) renders before the control’s content, end after it — and pushes itself to the far edge of the row, which becomes visible on wide or full-width controls. Sides are logical, so RTL layouts mirror automatically.
---
// GENERATED from packages/demos/affix/sides.demo.tsx — edit the source, not this file.
import Affix from "@pindoba/astro-affix";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { ArrowLeft, ArrowRight, RefreshCw } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "column",
width: "100%",
maxWidth: "320px",
})}
>
<Button
size="md"
emphasis="secondary"
passThrough={{ root: { style: { width: "100%" } } }}
>
<Affix>
<Stamp emphasis="ghost">
<ArrowLeft />
</Stamp>
</Affix>{" "}
Previous
</Button>
<Button
size="md"
emphasis="secondary"
passThrough={{ root: { style: { width: "100%" } } }}
>
Next{" "}
<Affix side="end">
<Stamp emphasis="ghost">
<ArrowRight />
</Stamp>
</Affix>
</Button>
<Button size="md" passThrough={{ root: { style: { width: "100%" } } }}>
<Affix>
<Stamp emphasis="ghost">
<RefreshCw />
</Stamp>
</Affix>{" "}
Sync now{" "}
<Affix side="end">
<Badge>3</Badge>
</Affix>
</Button>
</div>Set decorative when the addon is pure decoration — it’s hidden from assistive technology (aria-hidden) and transparent to pointer and selection, so clicks fall through to the control underneath. Leave it off when the addon carries real information, like a counter badge.
---
// GENERATED from packages/demos/affix/decorative.demo.tsx — edit the source, not this file.
import Affix from "@pindoba/astro-affix";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Sparkles } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Button size="md">
<Affix decorative>
<Stamp emphasis="ghost">
<Sparkles />
</Stamp>
</Affix>{" "}
Generate
</Button>
<Button size="md" emphasis="secondary">
Notifications{" "}
<Affix side="end">
<Badge tone="danger">4</Badge>
</Affix>
</Button>
</div>The passThrough prop provides two escape hatches: style accepts any Panda CSS SystemStyleObject applied to the affix root, and props forwards arbitrary HTML attributes such as data-testid or ARIA annotations.
---
// GENERATED from packages/demos/affix/custom.demo.tsx — edit the source, not this file.
import Affix from "@pindoba/astro-affix";
import Button from "@pindoba/astro-button";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Star } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Button size="md" emphasis="secondary">
<Affix passThrough={{ root: { style: { color: "warning.text" } } }}>
<Stamp emphasis="ghost">
<Star />
</Stamp>
</Affix>{" "}
Starred
</Button>
<Button size="md" emphasis="secondary">
Deploy{" "}
<Affix
side="end"
passThrough={{ root: { props: { "data-testid": "deploy-status" } } }}
>
<Badge tone="success">live</Badge>
</Affix>
</Button>
</div>