Why Not Astro? 🚀

Astro is a static site builder that is fast, flexible, and easy to use. Best of all, you can use it with your favorite frameworks.

Components? We've got 'em!

While not unique to Astro, you can build components in a .astro file you would in any other framework.

Headings

<Heading level="h3" size="2xl">Headings</Heading>

Heading 1

Font size: 4xl

Heading 2

Font size: 3xl

Heading 3

Font size: 2xl

Heading 4

Font size: xl

Heading 5

Font size: lg

Heading 6

Font size: base

---
import clsx from "clsx";

interface Props {
align?: "left" | "center" | "right";
class?: string;
level: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
size?: "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
}

const { 
align = "left",
class: clases,
level,
size = "base"
} = Astro.props;

const HeadingTag = level;
const headingClasses = clsx(
"font-bold",
"text-foreground", 
"text-balance",
{
    "text-4xl": size === "4xl",
    "text-3xl": size === "3xl",
    "text-2xl": size === "2xl",
    "text-xl": size === "xl",
    "text-lg": size === "lg",
    "text-base": size === "base",
    "text-sm": size === "sm",
},
{
    "text-left": align === "left",
    "text-center": align === "center",
    "text-right": align === "right",
},
{
    "tracking-tighter": size === "4xl" || size === "3xl" || size === "2xl",
    "tracking-tight": size === "xl" || size === "lg",
}
);
---

<HeadingTag class={clsx(headingClasses, clases)}>
  <slot />
</HeadingTag>

Buttons

This component should look familiar if you've ever used shadcn/ui. While you can use shadcn/ui, I prefer not to as there is no need for every component to be a React component.

---
import clsx from "clsx";

interface Props {
  variant?:
    | "default"
    | "destructive"
    | "warning"
    | "success"
    | "outline"
    | "secondary"
    | "ghost"
    | "link";
  size?: "default" | "sm" | "lg" | "icon";
  class?: string;
}

const {
  variant = "default",
  size = "default",
  class: className,
  ...props
} = Astro.props;

const buttonVariants = {
  base: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-bold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  variant: {
    default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
    destructive:
      "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
    warning: "bg-warning text-warning-foreground shadow-sm hover:bg-warning/90",
    success: "bg-success text-success-foreground shadow-sm hover:bg-success/90",
    outline:
      "border border-border bg-background shadow-sm hover:bg-accent hover:text-foreground hover:border-foreground",
    secondary:
      "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 hover:text-foreground",
    ghost: "hover:bg-accent hover:text-foreground",
    link: "text-primary underline-offset-4 hover:underline",
  },
  size: {
    default: "h-9 px-4 py-2",
    sm: "h-8 rounded-md px-3 text-xs",
    lg: "h-10 rounded-md px-8",
    icon: "h-9 w-9",
  },
};
---

<button
  class={clsx(
    buttonVariants.base,
    buttonVariants.variant[variant],
    buttonVariants.size[size],
    className,
  )}
  {...props}>
  <slot />
</button>

Use the framework of your choice

Fully-supported frameworks included are React, Vue, SolidJS, Svelte, Preact, and Alpine. All you need to get started is install the integration of your choice and Astro's CLI will do the rest.*

*This are extra steps when using multiple JSX frameworks like React, Preact, and SolidJS.

React

Counter: 0

import { useState } from "react";
import MinusIconReact from "./MinusIconReact.jsx";
import PlusIconReact from "./PlusIconReact.jsx";

const CounterReact = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p className="text-lg text-cyan-50">Counter: {count}</p>
      <div className="flex gap-2">
        <button
          onClick={increment}
          className="bg-success text-success-foreground px-4 py-2 rounded-sm">
          <PlusIconReact>
            <span className="sr-only">Increase count</span>
          </PlusIconReact>
        </button>
        <button
          onClick={decrement}
          className="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm">  
          <MinusIconReact>
            <span className="sr-only">Decrease count</span>
          </MinusIconReact>
        </button>
      </div>
    </div>
  );
};

export default CounterReact;

Vue

Counter: 0

<script setup>
  import { ref } from "vue";
  import PlusIconVue from "./PlusIconVue.vue";
  import MinusIconVue from "./MinusIconVue.vue";

  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };
</script>

<template>
  <div>
    <p class="text-lg text-green-50">Counter: {{ count }}</p>
    <div class="flex gap-2">
      <button
        class="bg-success text-success-foreground px-4 py-2 rounded-sm"
        @click="increment">
        <PlusIconVue>
          <span class="sr-only">Increment</span>
        </PlusIconVue>
      </button>
      <button
        class="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm"
        @click="decrement">
        <MinusIconVue>
          <span class="sr-only">Decrement</span>
        </MinusIconVue>
      </button>
    </div>
  </div>
</template>

Svelte

Counter: 0

<script>
    import MinusIconSvelte from './MinusIconSvelte.svelte';
    import PlusIconSvelte from './PlusIconSvelte.svelte';
    
    let count = 0;

    function increment() {
        count += 1;
    }

    function decrement() {
        count -= 1;
    }
</script>

<div>
    <p class="text-lg text-orange-50">Counter: {count}</p>
    <div class="flex gap-2">
        <button on:click={increment} class="bg-success text-success-foreground px-4 py-2 rounded-sm">
            <PlusIconSvelte>
                <span class="sr-only">Increase count</span>
            </PlusIconSvelte>
        </button>
        <button on:click={decrement} class="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm">
            <MinusIconSvelte>
                <span class="sr-only">Decrease count</span>
            </MinusIconSvelte>
        </button>
    </div>
</div>

Preact

Counter: 0

import { useState } from "preact/hooks";
import MinusIconPreact from "./MinusIconPreact.jsx";
import PlusIconPreact from "./PlusIconPreact.jsx";

const CounterPreact = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p className="text-lg text-violet-50">Counter: {count}</p>
      <div className="flex gap-2">
        <button
          onClick={increment}
          className="bg-success text-success-foreground px-4 py-2 rounded-sm">
          <PlusIconPreact>
            <span className="sr-only">Increase count</span>
          </PlusIconPreact>
        </button>
        <button
          onClick={decrement}
          className="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm">
          <MinusIconPreact>
            <span className="sr-only">Decrease count</span>
          </MinusIconPreact>
        </button>
      </div>
    </div>
  );
};

export default CounterPreact;

Solid

Counter: 0

import { createSignal } from "solid-js";
import MinusIconSolid from "./MinusIconSolid.jsx";
import PlusIconSolid from "./PlusIconSolid.jsx";

export default function CounterSolid() {
  const [count, setCount] = createSignal(0);

  const incrementSolid = () => {
    setCount(count() + 1);
  };

  const decrementSolid = () => {
    setCount(count() - 1);
  };

  return (
    <div>
      <p className="text-lg text-blue-50">Counter: {count()}</p>
      <div className="flex gap-2">
        <button
          onClick={incrementSolid}
          className="bg-success text-success-foreground px-4 py-2 rounded-sm">
          <PlusIconSolid>
            <span className="sr-only">Increase count</span>
          </PlusIconSolid>
        </button>
        <button
          onClick={decrementSolid}
          className="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm">
          <MinusIconSolid>
            <span className="sr-only">Decrease count</span>
          </MinusIconSolid>
        </button>
      </div>
    </div>
  );
}

Alpine

Counter:

<div x-data="{ count: 0 }">
  <p class="text-lg text-emerald-50">Counter: <span x-text="count"></span></p>
  <div class="flex gap-2">
    <button
      @click="count++"
      class="bg-success text-success-foreground px-4 py-2 rounded-sm">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-plus">
        <path d="M5 12h14" />
        <path d="M12 5v14" />
        <span class="sr-only">Increase count</span>
      </svg>
    </button>
    <button
      @click="count--"
      class="bg-destructive text-destructive-foreground px-4 py-2 rounded-sm">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-minus">
        <path d="M5 12h14" />
        <span class="sr-only">Decrease count</span>
      </svg>
    </button>
  </div>
</div>