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)