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.

---
// 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>

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.

---
// 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>

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.

---
// 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>

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.

---
// 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>
  • Components
  • Blocks