# Button

> Pill-shaped action button in six variants and three sizes.

- **Slug:** `button`
- **Kind:** atom · **Category:** Actions
- **Source:** ported from [Beautiful UI](https://github.com/slev12397/beautiful-ui/blob/main/components/atoms/Button.tsx) (MIT)
- **Docs page:** https://viewership.ai/ui/components/button
- **All files in one bundle:** https://viewership.ai/ui/components/button.bundle.txt
- **Structured JSON:** https://viewership.ai/ui/components/button.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/ui/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/button/`:

   - `Button.astro` (raw: https://viewership.ai/ui/raw/button/Button.astro.txt)

npm packages: none.

## Usage

```astro
---
import Button from "../components/viewership/button/Button.astro";
---

<Button />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import Button from "./Button.astro";

interface Props {
  variant?: string;
}
const { variant = "Variants" } = Astro.props;
---

{variant === "Variants" && (
  <div class="row">
    <Button variant="primary">Run report</Button>
    <Button variant="secondary">Save draft</Button>
    <Button variant="ghost">Preview</Button>
    <Button variant="accent">Publish post</Button>
    <Button variant="success">Approve</Button>
    <Button variant="quiet">Cancel</Button>
  </div>
)}
{variant === "Sizes" && (
  <div class="row">
    <Button variant="primary" size="xs">Extra small</Button>
    <Button variant="primary" size="sm">Small</Button>
    <Button variant="primary" size="md">Medium</Button>
  </div>
)}

<style>
  .row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px;
  }
</style>
```

## Variants

Demo variants: `Variants`, `Sizes`.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"primary" \| "secondary" \| "ghost" \| "accent" \| "success" \| "quiet"` | `"secondary"` | Visual style. |
| `size` | `"xs" \| "sm" \| "md"` | `"md"` | Height and padding. |
| `href` | `string` |  | Renders an <a> instead of a <button>. |
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | Button type (ignored for links). |

## Used by

[Approval Card](https://viewership.ai/ui/components/approval-card.md), [Recommendation Card](https://viewership.ai/ui/components/recommendation-card.md), [Diff Table](https://viewership.ai/ui/components/diff-table.md), [Selection Actions](https://viewership.ai/ui/components/selection-actions.md), [Agent Screen](https://viewership.ai/ui/components/agent-screen.md)

## Source

### `src/components/viewership/button/Button.astro`

```astro
---
/**
 * Button: pill-shaped action button.
 *
 * Ported from Beautiful UI `components/atoms/Button.tsx`
 * (MIT, © 2026 Shane Levine). Renders an <a> when `href` is set.
 *
 * @example
 * <Button variant="primary">Run report</Button>
 * <Button variant="quiet" size="xs" href="/docs">Docs</Button>
 */
import type { HTMLAttributes } from "astro/types";

type Variant = "primary" | "secondary" | "ghost" | "accent" | "success" | "quiet";
type Size = "xs" | "sm" | "md";

type Props = (HTMLAttributes<"button"> & HTMLAttributes<"a">) & {
  variant?: Variant;
  size?: Size;
  /** Render as a link. */
  href?: string;
  type?: "button" | "submit" | "reset";
};

const { variant = "secondary", size = "md", href, type = "button", class: className, ...rest } = Astro.props;
const Tag = href ? "a" : "button";
---

<Tag
  class:list={["vs-button", `vs-button--${variant}`, `vs-button--${size}`, className]}
  href={href}
  type={href ? undefined : type}
  {...rest}
>
  <slot />
</Tag>

<style>
  .vs-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    font: inherit;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
    transition:
      transform var(--vs-duration-fast) ease-out,
      background-color var(--vs-duration-fast) ease-out,
      opacity var(--vs-duration-fast) ease-out;
  }
  .vs-button:active {
    transform: scale(0.96);
  }
  .vs-button:focus-visible {
    outline: none;
    box-shadow: var(--vs-focus-ring);
  }
  .vs-button:disabled,
  .vs-button[aria-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
  }

  /* Sizes */
  .vs-button--xs {
    height: 28px;
    gap: 4px;
    padding: 0 10px;
    border-radius: var(--vs-radius-pill);
    font-size: 12px;
    font-weight: 400;
  }
  .vs-button--sm {
    height: 27px;
    gap: 6px;
    padding: 0 12px;
    border-radius: var(--vs-radius-pill);
    font-size: 13px;
  }
  .vs-button--md {
    gap: 8px;
    padding: 9px 16px;
    border-radius: var(--vs-radius-pill);
    font-size: 14px;
  }

  /* Variants */
  .vs-button--primary {
    background: var(--vs-ink);
    color: var(--vs-ink-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  }
  .vs-button--primary:hover {
    opacity: 0.9;
  }
  .vs-button--secondary {
    background: var(--vs-surface);
    color: var(--vs-ink);
    box-shadow: var(--vs-shadow-btn);
  }
  .vs-button--secondary:hover {
    background: var(--vs-inset);
  }
  .vs-button--secondary[aria-expanded="true"] {
    background: var(--vs-hover);
  }
  .vs-button--ghost {
    background: var(--vs-hover-2);
    color: var(--vs-ink);
  }
  .vs-button--ghost:hover {
    background: var(--vs-line-strong);
  }
  .vs-button--accent {
    background: var(--vs-accent);
    color: var(--vs-on-accent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  }
  .vs-button--accent:hover {
    background: color-mix(in oklab, var(--vs-accent) 86%, black);
  }
  .vs-button--success {
    background: var(--vs-green);
    color: var(--vs-surface);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  }
  .vs-button--success:hover {
    filter: brightness(0.95);
  }
  .vs-button--quiet {
    background: transparent;
    color: var(--vs-ink);
  }
  .vs-button--quiet:hover {
    background: var(--vs-hover);
  }

  @media (prefers-reduced-motion: reduce) {
    .vs-button:active {
      transform: none;
    }
  }
</style>
```
