Agent context packet

Structured metadata, source alternates, graph links, headings, series position, and diagram inventory for crawlers and agent readers.

Table of contents

  1. Components Are Functions
  2. Defining a Component
  3. Using a Component
  4. Props = Function Arguments
  5. Real Example: krowdev’s Header
  6. Slots — The “Body” of a Component
  7. krowdev’s Component Map
  8. Sources

Series context

Learn Astro 6

A ground-up guide to Astro for developers coming from Python, LaTeX, or scientific computing.

  1. The Mental Model
  2. File-Based Routing
  3. .astro Files
  4. Components
  5. Astro Layouts
  6. Content Collections
  7. Astro Styling
  8. Markdown and Code Blocks
  9. Build and Deploy

Entry facts

Kind
guide
Maturity
evergreen
Confidence
high
Origin
ai-drafted (AI-drafted, human-reviewed)
Author
Agent
Directed by
krow
Published
Modified
Words
1,060 (5 min read)
Series
learn-astro #4
Tags
astro, fundamentals
Prerequisites
Full corpus
/llms-full.txt
Readable corpus
/source/full-corpus/

Graph links

Prerequisites astro-files

Related astro-styling

Tagsastro, fundamentals

Diagram inventory

  1. krowdev's Component Map Mermaid SVG, Mermaid source, ASCII companion

Components

Reusable building blocks — like Python functions that return HTML.

/ directed by / / 5 min read
On this page

Components Are Functions

This assumes you understand .astro files — components are just .astro files that take props. Continue with layouts, then styling which is how component CSS is scoped.

A component is an .astro file that you import and use inside other .astro files. It accepts inputs (props) and returns HTML.

Analogy
# Python function
def badge(label: str, color: str = "gray") -> str:
return f'<span class="badge badge-{color}">{label}</span>'
# Usage
html = badge("beginner", color="green")

An Astro component is the same idea, but with .astro file syntax instead of a Python function.

Defining a Component

src/components/Badge.astro
---
// Define accepted props (like function parameters)
interface Props {
label: string;
color?: string; // ? means optional
}
// Destructure with defaults (like keyword args)
const { label, color = 'gray' } = Astro.props;
---
<span class={`badge badge-${color}`}>{label}</span>
<style>
.badge {
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-family: var(--font-mono), monospace;
}
</style>

Using a Component

---
// In any other .astro file
import Badge from '../components/Badge.astro';
---
<!-- Use it like an HTML tag -->
<Badge label="beginner" color="green" />
<Badge label="concept" />
<Badge label="advanced" color="red" />

Output:

<span class="badge badge-green">beginner</span>
<span class="badge badge-gray">concept</span>
<span class="badge badge-red">advanced</span>

Props = Function Arguments

PythonAstro
def func(x: str)interface Props { x: string }
def func(x: str = "hi")const { x = "hi" } = Astro.props
func(x="hello")<Component x="hello" />
Type error at runtimeType error at build time
Key Insight

Astro.props is the only way a component receives data from its parent. There’s no global state, no context magic. Data flows down through props — explicit and traceable.

Real Example: krowdev’s Header

The site header is a component that uses other components. Here’s the full lifecycle:

src/components/Header.astro

---
import ThemeToggle from './ThemeToggle.astro';
const pathname = Astro.url.pathname;
const navLinks = [
{ href: '/explore/', label: 'Explore' },
{ href: '/guide/', label: 'Guides' },
{ href: '/article/', label: 'Articles' },
];
function isActive(href) {
return pathname.startsWith(href);
}
---
<header class="header">
<nav class="header-inner">
<a href="/" class="logo">
<span class="logo-mark">k</span>
<span class="logo-text">krowdev</span>
</a>
<div class="nav-right">
{navLinks.map(link => (
<a href={link.href}
class:list={['nav-link', { active: isActive(link.href) }]}>
{link.label}
</a>
))}
<ThemeToggle />
</div>
</nav>
</header>
<!-- Compiled output for a wiki page (simplified) -->
<header class="header" data-astro-cid-3ef6ksr2>
<nav class="header-inner" data-astro-cid-3ef6ksr2>
<a href="/" class="logo" data-astro-cid-3ef6ksr2>
<span class="logo-mark" data-astro-cid-3ef6ksr2>k</span>
<span class="logo-text" data-astro-cid-3ef6ksr2>krowdev</span>
</a>
<div class="nav-right" data-astro-cid-3ef6ksr2>
<a href="/explore/"
class="nav-link" data-astro-cid-3ef6ksr2>
Explore
</a>
<a href="/guide/" class="nav-link active" data-astro-cid-3ef6ksr2>
Guides
</a>
<a href="/article/" class="nav-link" data-astro-cid-3ef6ksr2>
Articles
</a>
<!-- ThemeToggle component expanded inline -->
<button class="theme-toggle" aria-label="Toggle theme">
<svg class="icon-sun">...</svg>
<svg class="icon-moon">...</svg>
</button>
</div>
</nav>
</header>

Notice what happened at build time:

  • navLinks.map(...) expanded to three <a> tags
  • isActive(link.href) evaluated to true for Guides (we’re on a guide page), adding the active class
  • <ThemeToggle /> was replaced by its full HTML output (button + SVGs)
  • class:list resolved the conditional class to a plain class string
  • Scoped data-astro-cid-* attributes were added for CSS isolation

The component imported another component (ThemeToggle), computed which nav link is active, and mapped an array to HTML — all at build time.

Slots — The “Body” of a Component

Sometimes you want a component to wrap other content, not just receive props. That’s what <slot /> does:

src/components/Card.astro
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<div class="card">
<h3>{title}</h3>
<slot /> <!-- Child content goes here -->
</div>

Usage:

<Card title="Getting Started">
<p>This paragraph becomes the slot content.</p>
<p>So does this one.</p>
</Card>
Analogy

<slot /> is like *args or a callback in Python:

def card(title: str, body: str) -> str:
return f'<div class="card"><h3>{title}</h3>{body}</div>'

Except instead of passing HTML as a string argument, you put it between the opening and closing tags.

krowdev’s Component Map

Here’s how the actual components in krowdev relate:

Base.astro composes a content slot, Header (logo, nav, and the client-side ThemeToggle), and Footer. KBEntry extends Base and adds SeriesSidebar, an article slot, and TableOfContents.

How krowdev's components relateBase.astro composes a content slot, Header (logo, nav, and the client-side ThemeToggle), and Footer. KBEntry extends Base and adds SeriesSidebar, an article slot, and TableOfContents.

Base.astro (layout)

<slot /> — page content

Header.astro

logo + nav links

ThemeToggle.astro — only client-side JS

Footer.astro

KBEntry.astro (extends Base)

SeriesSidebar.astro — reads series entries

<slot /> — article content

TableOfContents.astro — reads headings

How krowdev's components relate

Every component does one thing. Data flows down through props. No hidden dependencies.

Challenge: Build a component

Create a Callout.astro component that renders a styled box with a label and slot content.

It should accept:

  • type prop: "info", "warning", or "tip"
  • Slot content for the body

Expected usage:

<Callout type="tip">
<p>This is a helpful tip!</p>
</Callout>

Try writing it in src/components/Callout.astro — define the interface, destructure props, use <slot />, add scoped styles.

One possible solution
---
interface Props {
type: 'info' | 'warning' | 'tip';
}
const { type } = Astro.props;
const labels = { info: 'Info', warning: 'Warning', tip: 'Tip' };
---
<div class={`callout callout-${type}`}>
<span class="label">{labels[type]}</span>
<slot />
</div>
<style>
.callout { padding: 1rem; border-left: 3px solid; border-radius: 0 8px 8px 0; margin: 1rem 0; }
.callout-info { border-color: var(--ctp-blue); }
.callout-warning { border-color: var(--ctp-yellow); }
.callout-tip { border-color: var(--ctp-green); }
.label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; }
</style>

Sources

Diagram

Drag to pan · scroll or pinch to zoom · Esc to close