Skip to main content
Astro components (.astro files) are the foundation of any Astro project. They are HTML-only templating components with no client-side runtime.

Component Structure

An Astro component consists of two main parts: the component script (JavaScript/TypeScript) and the component template (HTML).
---
// Component Script (optional)
// JavaScript/TypeScript code runs at build time
import Header from './Header.astro';
const greeting = 'Hello, world!';
---

<!-- Component Template -->
<!-- HTML with JSX-like expressions -->
<div>
  <Header />
  <h1>{greeting}</h1>
</div>
The component script is enclosed in code fences (---) and runs during the build. This code never reaches the browser.

Component Script

The component script runs at build time on the server. You can:
  • Import other components
  • Import data from other files
  • Fetch data from APIs or databases
  • Create variables to use in your template
src/components/BlogPost.astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { getEntry } from 'astro:content';

// Fetch data at build time
const post = await getEntry('blog', Astro.params.slug);
const { Content } = await post.render();

// Process data
const readingTime = calculateReadingTime(post.body);
const publishedDate = new Date(post.data.pubDate).toLocaleDateString();
---

<BaseLayout title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <p>Published: {publishedDate}{readingTime} min read</p>
    <Content />
  </article>
</BaseLayout>
Everything in the component script runs once at build time, not on every request.

Props

Components accept data through props, similar to React or Vue:
src/components/Card.astro
---
const { title, description, href } = Astro.props;
---

<article class="card">
  <h3>{title}</h3>
  <p>{description}</p>
  <a href={href}>Read more</a>
</article>
Use it:
---
import Card from './components/Card.astro';
---

<Card 
  title="First Post" 
  description="This is my first blog post"
  href="/blog/first-post"
/>

TypeScript Props

Define prop types with TypeScript:
src/components/Card.astro
---
interface Props {
  title: string;
  description: string;
  href: string;
  image?: string;
}

const { title, description, href, image } = Astro.props;
---

<article class="card">
  {image && <img src={image} alt={title} />}
  <h3>{title}</h3>
  <p>{description}</p>
  <a href={href}>Read more</a>
</article>
---
const { name, age } = Astro.props;
---
<p>{name} is {age} years old</p>

Slots

Slots allow you to pass child content to components:
src/components/Card.astro
---
const { title } = Astro.props;
---

<div class="card">
  <h2>{title}</h2>
  <div class="content">
    <slot />
  </div>
</div>
Use it:
<Card title="My Card">
  <p>This content goes in the slot!</p>
  <button>Click me</button>
</Card>

Named Slots

Define multiple slots with names:
src/components/BlogPost.astro
<article>
  <header>
    <slot name="header" />
  </header>
  
  <main>
    <slot />  <!-- default slot -->
  </main>
  
  <footer>
    <slot name="footer" />
  </footer>
</article>
Use named slots:
<BlogPost>
  <h1 slot="header">My Blog Post</h1>
  
  <p>This goes in the default slot.</p>
  <p>Multiple elements work too!</p>
  
  <p slot="footer">Written by Alice</p>
</BlogPost>

Fallback Content

Provide default content if no slot content is passed:
<div class="card">
  <slot>
    <p>No content provided</p>
  </slot>
</div>

Slot Implementation

From the source code at src/runtime/server/render/slot.ts, slots are rendered as functions that return template results:
export function renderSlot(
  result: SSRResult,
  slotted: ComponentSlotValue | RenderTemplateResult,
  fallback?: ComponentSlotValue | RenderTemplateResult,
): RenderInstance {
  if (!slotted && fallback) {
    return renderSlot(result, fallback);
  }
  return {
    async render(destination) {
      await renderChild(
        destination, 
        typeof slotted === 'function' ? slotted(result) : slotted
      );
    },
  };
}

The Astro Global

Every Astro component has access to the global Astro object:
---
// Route parameters from dynamic routes
const { id } = Astro.params;

// Component props
const { title } = Astro.props;

// Request information
const url = Astro.url;
const headers = Astro.request.headers;
---
Astro.props        // Component props
Astro.params       // Route parameters
Astro.request      // Request object
Astro.url          // Request URL
Astro.site         // Site URL from config
Astro.slots        // Slot utilities

Framework Components

Astro components can render framework components from React, Vue, Svelte, and more:
---
import ReactCounter from './ReactCounter.jsx';
import VueButton from './VueButton.vue';
import SvelteCard from './SvelteCard.svelte';
---

<div>
  <!-- By default, these render as static HTML -->
  <ReactCounter />
  <VueButton />
  
  <!-- Add client directive for interactivity -->
  <SvelteCard client:load />
</div>
Framework components are static by default. Add a client directive to make them interactive.

Component Rendering

From src/runtime/server/render/component.ts, Astro determines how to render each component:
export function renderComponent(
  result: SSRResult,
  displayName: string,
  Component: unknown,
  props: Record<string | number, any>,
  slots: ComponentSlots = {},
): RenderInstance | Promise<RenderInstance> {
  // Astro components
  if (isAstroComponentFactory(Component)) {
    return renderAstroComponent(result, displayName, Component, props, slots);
  }
  
  // Framework components (React, Vue, etc.)
  return renderFrameworkComponent(result, displayName, Component, props, slots);
}

Practical Examples

Reusable Button Component

src/components/Button.astro
---
interface Props {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  href?: string;
}

const { variant = 'primary', size = 'md', href } = Astro.props;
const Tag = href ? 'a' : 'button';
---

<Tag 
  class={`btn btn-${variant} btn-${size}`}
  href={href}
>
  <slot />
</Tag>

<style>
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 0.25rem;
    font-weight: 600;
  }
  
  .btn-primary {
    background: #3b82f6;
    color: white;
  }
  
  .btn-sm { font-size: 0.875rem; }
  .btn-md { font-size: 1rem; }
  .btn-lg { font-size: 1.125rem; }
</style>

Data-Fetching Component

src/components/UserProfile.astro
---
interface Props {
  userId: string;
}

const { userId } = Astro.props;

// Fetch data at build time
const response = await fetch(`https://api.example.com/users/${userId}`);
const user = await response.json();
---

<div class="profile">
  <img src={user.avatar} alt={user.name} />
  <h2>{user.name}</h2>
  <p>{user.bio}</p>
  <a href={`mailto:${user.email}`}>Contact</a>
</div>

Conditional Rendering

src/components/Alert.astro
---
interface Props {
  type: 'info' | 'warning' | 'error' | 'success';
  title?: string;
}

const { type, title } = Astro.props;

const icons = {
  info: '💡',
  warning: '⚠️',
  error: '❌',
  success: '✅',
};
---

<div class={`alert alert-${type}`}>
  <span class="icon">{icons[type]}</span>
  <div class="content">
    {title && <h4>{title}</h4>}
    <slot />
  </div>
</div>

Best Practices

1

Keep components focused

Each component should have a single responsibility.
2

Use TypeScript

Type your props for better developer experience and fewer bugs.
3

Extract reusable logic

Move complex logic to utility functions in the component script.
4

Use slots for flexibility

Slots make components more reusable and composable.
5

Default to Astro components

Only use framework components when you need client-side interactivity.

Learn More

Islands Architecture

Add interactivity with client directives

Layouts

Create reusable page structures

Build docs developers (and LLMs) love