component

Affix

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.

Basic

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>

Sides

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>

Decorative

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>

Custom Styling

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>
props · 6 shown · 6 total
children slot svelte
Snippet

No description yet.

decorative
boolean
default false

Marks 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.

element binding svelte
HTMLSpanElementnull

Bound reference to the root element (`bind:this`).

fit
"row""content"
default "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).

passThrough
AffixPassThrough<RootElementAttributes>

Per-slot style and HTML-attribute overrides.

side
"start""end"
default "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.

Type

  • Components
  • Blocks