component
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.
---
import Affix from "../Affix.astro";
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.
---
import Affix from "../Affix.astro";
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";
---
<!-- Full-width buttons make the edge behavior visible: `side="start"`
(default) sits before the content; `side="end"` pushes itself to the
far edge of the row. -->
<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.
---
import Affix from "../Affix.astro";
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",
})}
>
<!-- The sparkles icon repeats what the label says — `decorative` hides it
from assistive technology and lets clicks fall through. -->
<Button size="md">
<Affix decorative><Stamp emphasis="ghost"><Sparkles /></Stamp></Affix>
Generate
</Button>
<!-- The counter is real information — keep the default so it stays in
the accessibility tree. -->
<Button size="md" emphasis="secondary">
Notifications
<Affix side="end"><Badge feedback="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.
---
import Affix from "../Affix.astro";
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",
})}
>
<!-- PassThrough custom root styles -->
<Button size="md" emphasis="secondary">
<Affix passThrough={{ root: { style: { color: "warning.text" } } }}>
<Stamp emphasis="ghost"><Star /></Stamp>
</Affix>
Starred
</Button>
<!-- PassThrough with extra HTML attributes -->
<Button size="md" emphasis="secondary">
Deploy
<Affix
side="end"
passThrough={{ root: { props: { "data-testid": "deploy-status" } } }}
>
<Badge feedback="success">live</Badge>
</Affix>
</Button>
</div>No description yet.
falseMarks the affix as pure decoration: hidden from assistive technology (`aria-hidden`) and transparent to pointer/selection, so clicks fall through to the control underneath. Don't combine with interactive children.
Bound reference to the root element (`bind:this`).
"row"`row` (default) lets row-aware addons (Badge, Stamp, Kbd) size themselves to the control row. `content` opts the affix subtree out of that sizing — children keep their own intrinsic size (e.g. taller or multi-line content that should overflow the row).
Per-slot style and HTML-attribute overrides.
"start"Which edge of the control row the affix sits on. `start` renders before the control's content, `end` after (and pushes itself to the far edge). Drives the row's edge-padding flip via `data-affix="start|end"` — logical sides, so RTL mirrors automatically.
Plus all standard <span> HTML
attributes.