Skip to content

Astro Components — Building Reusable UI

DodaTech Updated 2026-06-28 4 min read

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

  1. Putting interactive logic in an Astro component: Astro components are server-only. Use framework components (React, Vue) with client:* directives for interactivity.
  2. Not typing props: Without TypeScript interfaces for props, you lose editor autocomplete and Type Checking.
  3. Using Astro.props without destructuring: Accessing Astro.props directly works but is verbose. Destructure in the frontmatter for cleaner templates.
  4. Forgetting the closing tag for void elements: Self-closing tags like <Card /> are valid, but <Card> without </Card> is not.
  5. Over-nesting components: More than 4-5 levels of component nesting makes the render tree hard to debug.

Practice Questions

  1. 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.

  2. What is the <slot /> element used for? Answer: It renders child content passed between the opening and closing tags of a component.

  3. 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.

  4. 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

Can I use `Astro.glob()` in any component?

: Yes. Astro.glob() works in any .astro component to import multiple files at build time.

What is the difference between `.astro` and `.jsx` components?

: .astro components are server-only templates. .jsx components render on the client with interactivity.

Do Astro components support CSS?

: Yes. Add <style> tags in the component. Astro scopes styles automatically.

Can I use TypeScript in component frontmatter?

: Yes. The frontmatter fence supports full TypeScript syntax.

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