Markdown source
.astro Files Markdown source
Readable source view for humans. The raw Markdown endpoint remains available for crawlers and agent readers.
---
title: ".astro Files"
description: "The core building block — a code fence that runs at build time and a template that outputs HTML."
kind: guide
maturity: evergreen
confidence: high
origin: ai-drafted
author: "Agent"
directedBy: "krow"
tags: [astro, fundamentals]
published: 2026-03-15
modified: 2026-05-31
wordCount: 1046
readingTime: 5
series: "learn-astro"
series_order: 3
prerequisites: [astro-file-routing]
url: https://krowdev.com/guide/astro-files/
---
## Agent Context
- Canonical: https://krowdev.com/guide/astro-files/
- Markdown: https://krowdev.com/guide/astro-files.md
- Full corpus: https://krowdev.com/llms-full.txt
- Kind: guide
- Maturity: evergreen
- Confidence: high
- Origin: ai-drafted
- Author: Agent
- Directed by: krow
- Published: 2026-03-15
- Modified: 2026-05-31
- Words: 1046 (5 min read)
- Tags: astro, fundamentals
- Series: learn-astro (#3)
- Prerequisites: astro-file-routing
- Content map:
- h2: Anatomy of an .astro File
- h2: The Code Fence
- h2: The Template
- h3: Template Rules
- h2: Scoped Styles
- h2: Real Example: krowdev's PostCard
- h2: Putting It Together
- h2: Sources
- Crawl policy: same canonical content is exposed through HTML, Markdown, and llms-full; no crawler-specific content gate.
## Anatomy of an .astro File
This builds on the [Astro mental model](/guide/astro-mental-model/) and [file routing](/guide/astro-file-routing/). The next concepts are [components](/guide/astro-components/), [layouts](/guide/astro-layouts/), and [styling](/guide/astro-styling/).
Every `.astro` file has two parts, separated by a code fence (`---`):
```astro
---
// PART 1: Component Script
// Runs at BUILD TIME on your machine
// This is TypeScript/JavaScript
const greeting = "Hello";
const items = ["one", "two", "three"];
---
<!-- PART 2: Component Template -->
<!-- Outputs HTML -->
<!-- Curly braces {expression} insert values -->
<h1>{greeting}, world</h1>
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
```
:::analogy
Think of it as a Jupyter notebook with exactly two cells:
1. **Code cell** (the fence) — compute values, import data, do logic
2. **Markdown/HTML cell** (the template) — display the results
The code cell runs once. The output cell is the HTML that ships to visitors.
:::
## The Code Fence
Everything between the `---` markers runs at **build time only**. It never reaches the browser. You can:
```astro
---
// Import other components
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
// Import data from content collections
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// Use any JavaScript — fetch APIs, read files, compute
const sorted = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
const total = sorted.length;
// Define TypeScript interfaces
interface Props {
title: string;
}
// Access page props
const { title } = Astro.props;
---
```
:::key
You can `await` at the top level in the code fence — no `async` wrapper needed. This is because the code runs at build time in Node.js, not in the browser.
:::
## The Template
Below the fence is HTML with `{expressions}` for dynamic values:
```astro
---
const name = "krow";
const isPublished = true;
const tags = ["astro", "webdev"];
---
<!-- Insert a value -->
<h1>Welcome to {name}dev</h1>
<!-- Conditional rendering -->
{isPublished && <span class="badge">Published</span>}
<!-- Ternary -->
<p>{isPublished ? "Live" : "Draft"}</p>
<!-- Loop (map returns elements) -->
<ul>
{tags.map(tag => <li>{tag}</li>)}
</ul>
<!-- Use a component -->
<Header />
```
### Template Rules
| Syntax | What It Does | Python Equivalent |
|---|---|---|
| `{value}` | Insert a value | `f"{value}"` |
| `{condition && <el/>}` | Render if truthy | `f"..." if condition else ""` |
| `{arr.map(x => <li>{x}</li>)}` | Loop | `[f"<li>{x}</li>" for x in arr]` |
| `<Component />` | Use another .astro file | Calling a function |
| `<slot />` | Where child content goes | `*args` placeholder |
:::warning
There's no `if/else` block in the template — use ternaries (`cond ? a : b`) or `&&` for conditionals. There's no `for` loop — use `.map()`. This comes from JSX conventions.
:::
## Scoped Styles
A `<style>` tag in an `.astro` file is **scoped by default** — its CSS only affects that component, not the rest of the page:
```astro
---
---
<h1>Title</h1>
<p>Body text</p>
<style>
/* This ONLY applies to the h1 in THIS file */
h1 {
color: purple;
font-size: 2rem;
}
</style>
```
Astro achieves this by adding unique `data-astro-*` attributes to elements and selectors at build time. You never see this — it just works.
:::analogy
Scoped styles are like namespacing in Python. `h1` in one file doesn't collide with `h1` in another, just as `module_a.process()` doesn't collide with `module_b.process()`.
:::
For styles that apply everywhere, use a separate `.css` file imported in a layout (like krowdev's `global.css`).
## Real Example: krowdev's PostCard
Here's an actual component from this site — `PostCard.astro`. Toggle between the source code you write, the HTML Astro compiles it to, and what the browser displays:
<p class="cv-label">src/components/PostCard.astro</p>
<nav class="cv-tabs">
<button class="cv-tab active" data-tab="source">Source</button>
<button class="cv-tab" data-tab="compiled">Compiled</button>
<button class="cv-tab" data-tab="rendered">Rendered</button>
</nav>
<div class="cv-panel active" data-panel="source">
```astro
---
interface Props {
title: string;
description?: string;
date: Date;
tags?: string[];
href: string;
}
const { title, description, date, tags = [], href } = Astro.props;
const formatted = date.toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric',
});
---
<a href={href} class="post-card">
<article>
<time datetime={date.toISOString()}>{formatted}</time>
<h3>{title}</h3>
{description && <p>{description}</p>}
{tags.length > 0 && (
{tags.map(tag => <span class="tag">{tag}</span>)}
)}
</article>
</a>
```
<div class="cv-panel" data-panel="compiled">
```html
<!-- What Astro outputs after build (simplified) -->
<a href="/article/welcome-to-krowdev/" class="post-card" data-astro-cid-kg7m>
<article data-astro-cid-kg7m>
<time datetime="2026-03-15T00:00:00.000Z" data-astro-cid-kg7m>
Mar 15, 2026
</time>
<h3 data-astro-cid-kg7m>Welcome to krowdev</h3>
<p data-astro-cid-kg7m>A second brain for everything learned...</p>
<div class="tags" data-astro-cid-kg7m>
<span class="tag" data-astro-cid-kg7m>meta</span>
</article>
</a>
```
<div class="cv-panel" data-panel="rendered">
Notice what changed:
- **`{formatted}`** became `Mar 15, 2026` — computed at build time
- **`{date.toISOString()}`** became the full ISO string — computed at build time
- **`{tags.map(...)}`** expanded to one `<span>` per tag
- **`data-astro-cid-*`** attributes were added for scoped CSS
- **No JavaScript shipped** — it's all static HTML
## Putting It Together
Here's how krowdev's homepage actually works:
```astro
---
// src/pages/index.astro
// 1. Import layout and content API
import Base from '../layouts/Base.astro';
import { getCollection } from 'astro:content';
// 2. Fetch and sort data at build time
const recent = (await getCollection('kb'))
.sort((a, b) => b.data.created.valueOf() - a.data.created.valueOf())
.slice(0, 3);
---
<!-- 3. Wrap in Base layout (provides <html>, header, footer) -->
<Base title="krowdev">
<section class="hero">
<h1>krowdev</h1>
</section>
<!-- 4. Render dynamic data -->
{recent.map(entry => (
<a href={`/${entry.data.kind}/${entry.id}/`}>
<h3>{entry.data.title}</h3>
</a>
))}
</Base>
<!-- 5. Scoped styles for this page only -->
<style>
.hero { padding: 5rem 1.5rem; text-align: center; }
</style>
```
**Challenge: Read a real .astro file**
Open krowdev's `PostCard.astro`:
```bash
cat src/components/PostCard.astro
```
Identify:
1. What **props** does it accept? (Look at the `interface Props`)
2. What **computation** happens in the code fence?
3. What **template logic** renders the tags? (Hint: look for `.map()`)
**Answer**
1. **Props:** `title` (string), `description` (optional string), `date` (Date), `tags` (optional string[]), `href` (string)
2. **Computation:** Formats the date using `toLocaleDateString()`
3. **Tags:** `{tags.map(tag => <span class="tag">{tag}</span>)}` — maps each tag string to a styled `<span>`
---
Previous: [File-Based Routing](/guide/astro-file-routing/) | Next: [Components](/guide/astro-components/)
## Sources
- Astro Docs, [Astro syntax](https://docs.astro.build/en/basics/astro-syntax/)
- Astro Docs, [Components](https://docs.astro.build/en/basics/astro-components/)
- MDN, [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)