component

Button

A versatile button component that supports multiple visual styles, sizes, and feedback states. Panel powers all surface rendering — giving buttons full access to backgrounds, borders, translucency, and radius controls without reimplementing them.

shape
size
Label
Emphasis
Feedback
Background
Border
Icon as label
disabled
translucent
Leading icon
Trailing icon

Emphasis

Four visual emphasis levels: primary uses a solid accent background, secondary uses a neutral surface background with a border, ghost is transparent until you hover it, and adaptive is transparent and inherits its text color from the parent — useful for buttons placed inside a colored panel or banner that should match the surrounding tone.

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "md",
    direction: "row",
    align: "center",
    flexWrap: "wrap",
  })}
>
  <Button emphasis="primary">Primary</Button>
  <Button emphasis="secondary">Secondary</Button>
  <Button emphasis="ghost">Ghost</Button>
  <Button emphasis="adaptive">Adaptive</Button>
</div>

Adaptive in context

emphasis="adaptive" shines when the button lives inside a colored container — the button picks up the parent’s colorPalette instead of imposing its own. The same trick works tonally on any other emphasis via feedback="inherit".

emphasis="adaptive" — transparent surface, inherits text color from each panel.

feedback="neutral"
feedback="primary"
feedback="success"
feedback="warning"
feedback="danger"

emphasis="primary" feedback="inherit" — keeps its filled treatment, but the accent surface follows the parent panel's palette.

feedback="neutral"
feedback="primary"
feedback="success"
feedback="warning"
feedback="danger"
---
import Button from "@pindoba/astro-button";
import Panel from "@pindoba/astro-panel";
import { stack } from "@pindoba/styled-system/patterns";
import { css } from "@pindoba/styled-system/css";

const feedbacks = [
  "neutral",
  "primary",
  "success",
  "warning",
  "danger",
] as const;
---

<div class={stack({ gap: "lg", direction: "column", align: "stretch" })}>
  <div class={stack({ gap: "sm", direction: "column", align: "stretch" })}>
    <p class={css({ fontSize: "sm", color: "panel.text.muted" })}>
      <strong>emphasis="adaptive"</strong> — transparent surface, inherits text color
      from each panel.
    </p>
    <div
      class={stack({
        gap: "sm",
        direction: "row",
        align: "stretch",
        flexWrap: "wrap",
      })}
    >
      {
        feedbacks.map((feedback) => (
          <Panel feedback={feedback} padding="md" radius="lg" border="muted">
            <div
              class={stack({
                gap: "2xs",
                direction: "column",
                align: "flex-start",
              })}
            >
              <span
                class={css({
                  fontSize: "xs",
                  color: "panel.text.muted",
                  textTransform: "uppercase",
                  letterSpacing: "wider",
                })}
              >
                feedback="{feedback}"
              </span>
              <Button emphasis="adaptive" size="sm">
                Take action
              </Button>
            </div>
          </Panel>
        ))
      }
    </div>
  </div>

  <div class={stack({ gap: "sm", direction: "column", align: "stretch" })}>
    <p class={css({ fontSize: "sm", color: "panel.text.muted" })}>
      <strong>emphasis="primary" feedback="inherit"</strong> — keeps its filled treatment,
      but the accent surface follows the parent panel's palette.
    </p>
    <div
      class={stack({
        gap: "sm",
        direction: "row",
        align: "stretch",
        flexWrap: "wrap",
      })}
    >
      {
        feedbacks.map((feedback) => (
          <Panel feedback={feedback} padding="md" radius="lg" border="muted">
            <div
              class={stack({
                gap: "2xs",
                direction: "column",
                align: "flex-start",
              })}
            >
              <span
                class={css({
                  fontSize: "xs",
                  color: "panel.text.muted",
                  textTransform: "uppercase",
                  letterSpacing: "wider",
                })}
              >
                feedback="{feedback}"
              </span>
              <Button emphasis="primary" feedback="inherit" size="sm">
                Confirm
              </Button>
            </div>
          </Panel>
        ))
      }
    </div>
  </div>
</div>

Feedback

Apply semantic feedback colors across all emphasis levels. The default feedback uses the primary color palette.

Primary emphasis

Secondary emphasis

Ghost emphasis

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Primary emphasis</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="primary">Default</Button>
      <Button emphasis="primary" feedback="success">Success</Button>
      <Button emphasis="primary" feedback="danger">Danger</Button>
      <Button emphasis="primary" feedback="warning">Warning</Button>
    </div>
  </div>

  <div>
    <h3>Secondary emphasis</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="secondary">Default</Button>
      <Button emphasis="secondary" feedback="primary">Primary</Button>
      <Button emphasis="secondary" feedback="success">Success</Button>
      <Button emphasis="secondary" feedback="danger">Danger</Button>
      <Button emphasis="secondary" feedback="warning">Warning</Button>
    </div>
  </div>

  <div>
    <h3>Ghost emphasis</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="ghost">Default</Button>
      <Button emphasis="ghost" feedback="primary">Primary</Button>
      <Button emphasis="ghost" feedback="success">Success</Button>
      <Button emphasis="ghost" feedback="danger">Danger</Button>
      <Button emphasis="ghost" feedback="warning">Warning</Button>
    </div>
  </div>
</div>

Size

Buttons come in five sizes to accommodate different layout needs.

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div
  class={stack({
    gap: "md",
    direction: "row",
    align: "center",
    flexWrap: "wrap",
  })}
>
  <Button size="xs">Extra Small</Button>
  <Button size="sm">Small</Button>
  <Button size="md">Medium</Button>
  <Button size="lg">Large</Button>
  <Button size="xl">Extra Large</Button>
</div>

Shape

The shape prop adds pill, square, or circle variants. Square and circle constrain the button to equal width and height — ideal for icon-only buttons. All shapes maintain their size-proportional height from the shared sizing system.

Shapes

Sizes

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Shapes</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button size="md">Default</Button>
      <Button shape="pill" size="md">Pill</Button>
      <Button shape="square" size="md">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
        </svg>
      </Button>
      <Button shape="circle" size="md">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
      </Button>
    </div>
  </div>

  <div>
    <h3>Sizes</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button shape="pill" size="xs">Pill xs</Button>
      <Button shape="pill" size="sm">Pill sm</Button>
      <Button shape="pill" size="md">Pill md</Button>
      <Button shape="pill" size="lg">Pill lg</Button>
    </div>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
        mt: "sm",
      })}
    >
      <Button shape="square" size="xs">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
        </svg>
      </Button>
      <Button shape="square" size="sm">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
        </svg>
      </Button>
      <Button shape="square" size="md">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
        </svg>
      </Button>
      <Button shape="square" size="lg">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
        </svg>
      </Button>
    </div>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
        mt: "sm",
      })}
    >
      <Button shape="circle" size="xs">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
      </Button>
      <Button shape="circle" size="sm">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
      </Button>
      <Button shape="circle" size="md">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
      </Button>
      <Button shape="circle" size="lg">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
      </Button>
    </div>
  </div>
</div>

Border

Control the border style using the border prop. secondary buttons default to border="muted". All border styles use box-shadow and do not affect button dimensions.

Secondary with border

Ghost with border

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Secondary with border</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="secondary" border="none">None</Button>
      <Button emphasis="secondary" border="default">Default</Button>
      <Button emphasis="secondary" border="bold">Bold</Button>
      <Button emphasis="secondary" border="muted">Muted</Button>
    </div>
  </div>

  <div>
    <h3>Ghost with border</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="ghost" border="none">None</Button>
      <Button emphasis="ghost" border="default">Default</Button>
      <Button emphasis="ghost" border="bold">Bold</Button>
      <Button emphasis="ghost" border="muted">Muted</Button>
    </div>
  </div>
</div>

Border Radius

Override the default size-proportional border radius with the radius prop. Granular edge and corner props (radiusTop, radiusLeft, radiusTopLeft, etc.) allow asymmetric rounding for grouped or nested layouts.

Border radius

Partial radius

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Border radius</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button radius="none">None</Button>
      <Button radius="sm">Small</Button>
      <Button radius="md">Medium</Button>
      <Button radius="xl">XL</Button>
      <Button radius="full">Full</Button>
    </div>
  </div>

  <div>
    <h3>Partial radius</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button radiusTop="full">Top</Button>
      <Button radiusBottom="full">Bottom</Button>
      <Button radiusLeft="full">Left</Button>
      <Button radiusRight="full">Right</Button>
    </div>
  </div>
</div>

Accent & Background

emphasis="primary" uses accent=true by default — a solid full-color (500) background with contrast text. emphasis="secondary" exposes the background prop for surface.peak through surface.ground and transparent options. accent is not available on secondary.

Primary (filled by default)

Secondary with different backgrounds

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Primary (filled by default)</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="primary">Action</Button>
      <Button emphasis="primary" feedback="success">Success</Button>
      <Button emphasis="primary" feedback="danger">Danger</Button>
      <Button emphasis="primary" feedback="warning">Warning</Button>
    </div>
  </div>

  <div>
    <h3>Secondary with different backgrounds</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button emphasis="secondary" background="surface.peak">Surface 1</Button>
      <Button emphasis="secondary" background="surface.base">Surface 3</Button>
      <Button emphasis="secondary" background="surface.ground">Surface 5</Button
      >
      <Button emphasis="secondary" background="transparent">Transparent</Button>
    </div>
  </div>
</div>

Icons & addons (Affix)

Compose icons, stamps, or badges as children wrapped in <Affix>: side="start" (default) places the addon before the label, side="end" after it, pushed to the far edge. The button’s edge padding tightens automatically on the affix side, and row-aware addons (Badge, Stamp, Kbd) size themselves to the control row. Inner badges or stamps using emphasis="adaptive" follow the button’s text color across all button emphases.

Both slots

Leading only

Trailing only

---
import Button from "@pindoba/astro-button";
import Affix from "@pindoba/astro-affix";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Download } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";

const sizes = ["xs", "sm", "md", "lg"] as const;
---

<div class={stack({ gap: "lg", direction: "column", align: "flex-start" })}>
  <div class={stack({ gap: "md", direction: "column", align: "flex-start" })}>
    <h4>Both slots</h4>
    {
      sizes.map((size) => (
        <Button emphasis="primary" size={size}>
          <Affix>
            <Stamp
              emphasis="primary"
              feedback="primary"
              background="surface.ground"
              border="none"
            >
              <Download />
            </Stamp>
          </Affix>
          Download
          <Affix side="end">
            <Badge>2.3 MB</Badge>
          </Affix>
        </Button>
      ))
    }
  </div>

  <div class={stack({ gap: "md", direction: "column", align: "flex-start" })}>
    <h4>Leading only</h4>
    {
      sizes.map((size) => (
        <Button emphasis="primary" size={size}>
          <Affix>
            <Stamp
              emphasis="primary"
              feedback="primary"
              background="surface.ground"
              border="none"
            >
              <Download />
            </Stamp>
          </Affix>
          Download
        </Button>
      ))
    }
  </div>

  <div class={stack({ gap: "md", direction: "column", align: "flex-start" })}>
    <h4>Trailing only</h4>
    {
      sizes.map((size) => (
        <Button emphasis="primary" size={size}>
          Download
          <Affix side="end">
            <Badge>2.3 MB</Badge>
          </Affix>
        </Button>
      ))
    }
  </div>
</div>

Custom Styling

The passThrough prop provides two escape hatches for advanced customization: style accepts any Panda CSS SystemStyleObject applied directly to the button root, and props forwards arbitrary HTML attributes. Use style for layout overrides and props for accessibility attributes like aria-label or data-testid.

Custom style via passThrough

Custom props via passThrough

---
import Button from "../Button.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <div>
    <h3>Custom style via passThrough</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button
        passThrough={{
          root: {
            style: { minWidth: "200px", justifyContent: "start" },
          },
        }}
      >
        Wide left-aligned
      </Button>
    </div>
  </div>

  <div>
    <h3>Custom props via passThrough</h3>
    <div
      class={stack({
        gap: "md",
        direction: "row",
        align: "center",
        flexWrap: "wrap",
      })}
    >
      <Button
        emphasis="secondary"
        passThrough={{
          root: {
            props: { "aria-label": "Submit form", "data-testid": "submit-btn" },
          },
        }}
      >
        Submit
      </Button>
    </div>
  </div>
</div>
props · 19 shown · 19 total
accent
boolean
default false

Use the full-color (500) accent background from the current `feedback` color with contrast text. Only available on `primary` and `ghost` emphasis — `primary` sets this to `true` by default.

background
"surface.peak""surface.hill""surface.base""surface.valley""surface.ground""transparent"
default "surface.peak"

Background style — only available when `emphasis="secondary"`. `surface.peak`: lightest. `surface.hill–3`: intermediate. `surface.ground`: darkest. `transparent`: no background.

border
"none""bold""default""muted""accent"
default "none" ("muted" for emphasis="secondary")

Border style. `none`: no border. `default`: standard border. `bold`: stronger emphasis. `muted`: subtle separation.

children slot svelte
Snippet

No description yet.

element binding svelte
HTMLButtonElementnull

Bound reference to the underlying `<button>` element (`bind:this`).

emphasis
"primary""secondary""ghost""adaptive"
default "primary"

Visual emphasis level. `primary`: solid accent background with contrast text. `secondary`: neutral surface with a border. `ghost`: transparent until hovered. `adaptive`: transparent and inherits text color from the parent — pair with `feedback="inherit"` to fully follow the context.

feedback
"neutral""primary""success""warning""danger""inherit"
default "primary" when emphasis="primary", "inherit" when emphasis="adaptive", "neutral" otherwise

Semantic color tone. `neutral`: greyscale. `primary`: brand palette. `success`: green. `warning`: orange. `danger`: red. `inherit`: keep the ambient `colorPalette` from the parent unchanged — combine with `emphasis="adaptive"` for a fully context-following button.

leading slot svelte
Snippet

No description yet.

passThrough
ButtonPassThrough<RootElementAttributes>

Per-slot style and HTML-attribute overrides (`root`). The slot accepts `style` (a Panda style object merged into the slot's class) and `props` (extra attributes spread onto the element). Icon/addon boxes are composed as `<Affix>` children, which carry their own `passThrough`.

radius
"inherit""sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner"

Border-radius override. Accepts the full spacing scale plus `full` for fully rounded corners. Overrides the default size-proportional radius.

radiusBottom
"inherit""sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner"

Border radius for the bottom-left and bottom-right corners.

radiusLeft
"inherit""sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner"

Border radius for the top-left and bottom-left corners.

radiusRight
"inherit""sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner"

Border radius for the top-right and bottom-right corners.

radiusTop
"inherit""sm""md""lg""xl""2xl""none""xs""3xl""4xl""5xl""6xl""full""2xs""inner"

Border radius for the top-left and top-right corners.

shape
"square""pill""circle"

Shape variant. `pill`: fully rounded corners. `circle`: equal width/height circle. `square`: equal width/height square.

size
"sm""md""lg""xl""xs"
default "md"

Size variant. Shares the same height system as inputs.

svgFit
boolean
default false

Constrain a child SVG to the button's content box (`width`/`height: 100%`), useful for icon-only buttons.

trailing slot svelte
Snippet

No description yet.

translucent
boolean
default false

Apply a frosted-glass effect with backdrop blur. Works with surface backgrounds.

Plus all standard <button> HTML attributes.

Type

  • Components
  • Blocks