Astro UI
Button.
Pill-shaped action button in six variants and three sizes.
ActionsAtomNo dependencies Beautiful UI, MIT
Install
-
Import the foundation once
foundation.css holds the tokens every component reads.
--- // src/layouts/Layout.astro import "../styles/foundation.css"; --- -
Use it
--- import Button from "../components/viewership/button/Button.astro"; --- <Button />
Props 4
| 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). |
Source 1 file
---
/**
* 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> Ported from Beautiful UI, MIT. Notices.
Used by Approval CardRecommendation CardDiff TableSelection ActionsAgent Screen