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.

Emphasis

Three visual emphasis levels: primary uses a solid accent background, secondary uses a surface background with a muted border, and ghost uses a transparent background.

---
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>
</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 four 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>
</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 elevated, sunken, and transparent options. accent is not available on secondary.

Primary (accent 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 (accent 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">Surface</Button>
      <Button emphasis="secondary" background="elevated">Elevated</Button>
      <Button emphasis="secondary" background="sunken">Sunken</Button>
      <Button emphasis="secondary" background="transparent">Transparent</Button>
    </div>
  </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

Prop
emphasis
emphasis

Visual emphasis level. primary: Solid accent background with contrast text. secondary: Surface background with a muted border. ghost: Transparent background.

Type "primary" | "secondary" | "ghost"
Default "primary"
Required No
feedback
feedback

Semantic feedback color scheme. default: Primary color palette. success: Green. danger: Red. warning: Orange.

Type "default" | "success" | "danger" | "warning"
Default "default"
Required No
size
size

Size variant. All sizes share the same height system as inputs. xs: Extra small. sm: Small. md: Default. lg: Large.

Type "xs" | "sm" | "md" | "lg"
Default "md"
Required No
shape
shape

Shape variant. pill: Fully rounded corners. circle: Equal width/height circle. square: Equal width/height square.

Type "pill" | "circle" | "square"
Default undefined
Required No
background
background

Background style — only available when emphasis="secondary". surface: Default surface. sunken: Recessed background. elevated: Raised with shadow. transparent: No background.

Type "surface" | "sunken" | "elevated" | "transparent"
Default "surface"
Required No
accent
accent

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.

Type boolean
Default false
Required No
border
border

Box-shadow border style. none: No border. default: Standard border. bold: Stronger emphasis using a bolder color. muted: Subtle separation. Defaults to muted for secondary, none otherwise.

Type "none" | "default" | "bold" | "muted"
Default "none" (secondary: "muted")
Required No
translucent
translucent

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

Type boolean
Default false
Required No
radius
radius

Border radius override. Accepts the full spacing scale from none to 11xl, plus full for fully rounded corners. Overrides the default size-proportional radius.

Type "none" | "5xs" | "4xs" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl" | "8xl" | "9xl" | "10xl" | "11xl" | "full"
Default undefined
Required No
radiusTop
radiusTop

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

Type SpacingScale
Default undefined
Required No
radiusBottom
radiusBottom

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

Type SpacingScale
Default undefined
Required No
radiusLeft
radiusLeft

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

Type SpacingScale
Default undefined
Required No
radiusRight
radiusRight

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

Type SpacingScale
Default undefined
Required No
radiusTopLeft
radiusTopLeft

Border radius for the top-left corner only.

Type SpacingScale
Default undefined
Required No
radiusTopRight
radiusTopRight

Border radius for the top-right corner only.

Type SpacingScale
Default undefined
Required No
radiusBottomLeft
radiusBottomLeft

Border radius for the bottom-left corner only.

Type SpacingScale
Default undefined
Required No
radiusBottomRight
radiusBottomRight

Border radius for the bottom-right corner only.

Type SpacingScale
Default undefined
Required No
passThrough
passThrough

Custom styling and props for button elements

Type { root?: { style?: SystemStyleObject; props?: Record<string, unknown> } }
Default undefined
Required No
...rest
...rest

Standard HTML button attributes

Type HTMLAttributes<HTMLButtonElement>
Default -
Required No