Skip to main content

Button Component

A ready-to-use button component for Astro projects with built-in support for multiple variants, sizes, and automatic link rendering.

---
import type { HTMLAttributes } from 'astro/types';

interface Props extends HTMLAttributes<'button'> {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  href?: string;
  target?: string;
  rel?: string;
  isExternal?: boolean;
}

const {
  variant = 'primary',
  size = 'md',
  href,
  target,
  rel,
  isExternal,
  class: className,
  ...rest
} = Astro.props;

const Tag = href ? 'a' : 'button';
const finalRel = isExternal ? (rel || 'noopener noreferrer') : rel;
const finalTarget = isExternal ? (target || '_blank') : target;

const elementProps = href
  ? { href, target: finalTarget, rel: finalRel, ...rest }
  : { type: (rest.type || 'button'), ...rest };
---

<Tag
  class:list={['btn', `btn-${variant}`, `btn-${size}`, className]}
  {...elementProps}
>
  <slot />
</Tag>
/* button.scss */

$variants: (
  "primary": #007bff,
  "secondary": #6c757d,
  "outline": transparent,
  "ghost": transparent
);

.btn {
  display: inline-flex;
  border-radius: 4px;
  font-weight: 500;

  // Size Mapping
  &-sm { padding: 0.25rem 0.5rem; }
  &-md { padding: 0.5rem 1rem; }
  &-lg { padding: 0.75rem 1.5rem; }

  // Dynamic Variant Generation
  @each $name, $color in $variants {
    &-#{$name} {
      background-color: $color;

      @if $name == "outline" {
        border: 1px solid #007bff;
        color: #007bff;
      } @else if $name == "ghost" {
        color: inherit;
      } @else {
        color: #fff;
      }
    }
  }
}