Astro Components — Building Reusable UI
Learn how to create Astro components with props, slots, HTML templates, and server-side logic for reusable UI across your entire website.
In this lesson, you'll write .astro components that accept props, use default slots, compose other components, and run server-side logic during build time. Astro components render to static HTML with zero JavaScript overhead.
What You'll Learn
How to create components, accept and type props, use slots for children, compose components together, and leverage server-side data in components.
Why It Matters
Components are the core of Astro's architecture. They let you build complex UIs from small, reusable pieces without sending unnecessary JavaScript to the browser.
Real-World Use
DodaTech's documentation site uses dozens of small Astro components for cards, callouts, code blocks, and navigation menus that render as pure HTML.
flowchart LR
A[Component] --> B[Template HTML]
A --> C[Server Script]
B --> D[Static HTML Output]
C --> E[Data at Build Time]
style A fill:#ff5a03,color:#fff
Creating a Component
Create src/components/Card.astro:
---
export interface Props {
title: string;
description: string;
link?: string;
}
const { title, description, link } = Astro.props;
---
<article class="card">
<h2>{title}</h2>
<p>{description}</p>
{link && <a href={link}>Learn more</a>}
</article>
Use it in a page:
---
import Card from "../components/Card.astro";
---
<Card title="Astro Basics" description="Learn the fundamentals of Astro" link="/astro/basics" />
<Card title="Content Collections" description="Manage content with type safety" />
Output: Each <Card /> renders as an <article> with the provided props. The second card omits the link since it's optional.
Components with Slots
Components can accept children via the <slot /> element:
---
// src/components/Badge.astro
export interface Props {
variant: "info" | "warning" | "error";
}
const { variant } = Astro.props;
---
<span class={`badge badge-${variant}`}>
<slot />
</span>
Usage:
<Badge variant="info">New feature available</Badge>
<Badge variant="warning">Deprecated API</Badge>
Output: The badge text is passed as child content and rendered inside the <span> with the appropriate CSS class.
Composing Components
Components can use other components:
---
// src/components/CardGrid.astro
import Card from "./Card.astro";
export interface Props {
items: Array<{ title: string; description: string }>;
}
const { items } = Astro.props;
---
<div class="grid">
{items.map(item => (
<Card title={item.title} description={item.description} />
))}
</div>
Usage in a page:
---
import CardGrid from "../components/CardGrid.astro";
const features = [
{ title: "Fast", description: "Zero JS by default" },
{ title: "Flexible", description: "Any UI framework" },
];
---
<CardGrid items={features} />
Output: The grid component maps over the data and renders a <Card> for each item. All HTML is generated at build time.
Server-Side Logic
Components can run data fetching and processing in their frontmatter:
---
// src/components/RecentPosts.astro
const posts = await Astro.glob("../pages/blog/*.md");
const recent = posts.slice(0, 3);
---
<ul>
{recent.map(post => (
<li><a href={post.url}>{post.frontmatter.title}</a></li>
))}
</ul>
Output: At build time, Astro reads all blog markdown files, selects the three most recent, and renders them as a static list.
Common Mistakes
- Putting interactive logic in an Astro component: Astro components are server-only. Use framework components (React, Vue) with
client:*directives for interactivity. - Not typing props: Without TypeScript interfaces for props, you lose editor autocomplete and Type Checking.
- Using
Astro.propswithout destructuring: AccessingAstro.propsdirectly works but is verbose. Destructure in the frontmatter for cleaner templates. - Forgetting the closing tag for void elements: Self-closing tags like
<Card />are valid, but<Card>without</Card>is not. - Over-nesting components: More than 4-5 levels of component nesting makes the render tree hard to debug.
Practice Questions
How do you pass data to an Astro component? Answer: Through component props. Define props in the component's frontmatter interface and pass them as HTML attributes.
What is the
<slot />element used for? Answer: It renders child content passed between the opening and closing tags of a component.Can Astro components use React hooks? Answer: No. Astro components run only on the server. Client-side hooks belong in framework components with
client:*directives.How do you make a component optional prop? Answer: Use
?in the TypeScript interface, e.g.,link?: string. Check with{link && ...}in the template.
Challenge
Build a testimonial component that accepts an author name, quote, and optional avatar URL. Display testimonials in a grid using a grid component.
Mini Project
Create a feature comparison table component: accept an array of features with names and supported/unsupported status, render them as a styled table, and use it on a product comparison page.
FAQ
What's Next
Explore Astro Islands Architecture to understand Astro's unique approach to client-side interactivity.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro