Skip to content

Latest commit

 

History

History
415 lines (282 loc) · 9.47 KB

File metadata and controls

415 lines (282 loc) · 9.47 KB

The .astro File Format - Syntax Specification

Version: 1.0 Status: Draft Date: 2026-02-03


Table of Contents

  1. File Structure
  2. Component Script (Frontmatter)
  3. Template
  4. Style Blocks
  5. Script Blocks

1. File Structure

An .astro file is composed of up to two sections described below. All are optional. When present, they must appear in this order:

┌──────────────────────────────────┐
│  ---                             │
│  Component Script                │
│  ---                             │
├──────────────────────────────────┤
│  Template                        │
└──────────────────────────────────┘

1.1 Minimal examples

<!-- A comment! -->
<h1>Hello, World!</h1>
---
const greeting = "Hello";
---

<h1>{greeting}, World!</h1>

<style>
  h1 { color: royalblue; }
</style>

2. Component Script (Frontmatter)

The region between the two --- fences.

  • The opening and closing fences are not required on their own line. Code may appear on the same line as both fences.
  • Only one component script is allowed per file.
  • Any amount of whitespace may appear before the opening fence or after the closing fence.
  • Any content may appear before the opening fence, but is customarily ignored.

The component script is TypeScript. All standard TypeScript syntax is valid, apart from the exceptions and additions outlined in §2.1.

2.1 Top-level return

return may be used at the top level:

---
import { getUser } from "../lib/auth.js";

const user = await getUser();
if (!user) {
  return Astro.redirect("/login");
}
---

3. Template

The template is considered to be everything after the closing fence of the component script, or the entire file when there is no component script.

The template mostly follows the JSX specification, with the differences and additions outlined in §3.2.

3.1 Whitespace between the component script and template is ignored

Any amount of whitespace (spaces, tabs, newlines) between the closing fence of the component script and the start of the template is ignored and does not produce text nodes.

---
const greeting = "Hello";
---



<h1>{greeting}, World!</h1>

3.2 Differences from JSX

Unless mentioned otherwise, these differences apply both within the template and within expressions inside the template.

HTML comments

HTML comments <!-- … --> are allowed.

<!-- This is an HTML comment -->
{
	<!-- This is an HTML comment inside an expression -->
}

In addition to the standard JSX fragment syntax <>…</>, Fragment is supported as the tag name for fragments, i.e. <Fragment>…</Fragment>.

<Fragment>
	<div>Item 1</div>	
	<div>Item 2</div>
</Fragment>

This form may accept attributes, unlike the shorthand syntax.

is:raw Directive

The is:raw attribute on any element allows the content to be treated as raw text instead of JSX.

<Component is:raw>{<% non-JS content %>}</Component>

<div is:raw>
	{not an expression, just text}
</div>

HTML doctype

The HTML doctype declaration is allowed.

<!DOCTYPE html>
Top-level text nodes

Top-level text inside the template is treated as text nodes.

Hello, World!

Whitespace in expressions

Whitespace inside expressions { } is preserved as text nodes, unlike JSX, where whitespace inside expression containers is ignored:

<!-- Whitespace around element -->
{
    <div>Hello</div>
}

<!-- Leading/trailing spaces -->
{   <div>test</div>   }

<!-- Whitespace-only expression -->
{   }

In Astro, all of these produce text nodes for the whitespace, whereas in JSX:

  • Whitespace around elements inside { } is ignored
  • Whitespace-only expressions result in an empty expression, with no text nodes.

Multiple root elements

Unlike JSX, no single root element is required:

<header>…</header>
<main>…</main>
<footer>…</footer>

<!-- or inside an expression: -->
{
  <div>1</div>
  <div>2</div>
  <div>3</div>
}

Attribute names

Attribute names follow the HTML conventions and are not required to be valid JavaScript identifiers. For example, characters like hyphens and colons are allowed in attribute names:

<div @click="handler" x.data="value" :class="my-class" />

Namespace in component names is not supported

Colons in component names are not treated as namespace separators. For example:

<Namespace:Component />

Will be treated as a single component name (i.e. Namespace:Component). Spaces are not allowed in component names, so the following:

<Namespace : Component />

Would result in the component's name being Namespace, an attribute named : with no value, and an attribute named Component with no value.

Attribute shorthand

Attributes can use a shorthand syntax where {prop} is equivalent to prop={prop}:

<Component {prop} />
<!-- equivalent to: -->
<Component prop={prop} />

Template literal attributes

Attributes can use backticks for interpolation without opening an expression:

<Component attribute=`hello ${value}` />

Empty expressions inside attributes

Empty expressions {} inside attributes are allowed:

<Component attribute={} />

Comments inside opening tags

Comments are allowed inside the opening tag of an element:

<div
	{/* Hello */}
	class="my-class"
></div>

Less-than signs in text nodes

Less-than signs < in text nodes are parsed following HTML rules, meaning they do not need to be escaped:

<p>5 < 10</p>

Non-ASCII tag names are not supported

Tag names must use ASCII characters only. Non-ASCII tag names (e.g. <日本>) are not supported and are treated as text nodes.

Unquoted attribute values

Attribute values do not need to be quoted if they contain only alphanumeric characters, hyphens, underscores, and periods:

<Component data-id=12345 class=my-class />

Unclosed HTML tags

Like HTML, tags do not need to be explicitly closed.

<p>Hello
<p>World

It is up to the parser to optionally try to infer where tags close based on HTML parsing rules, or leave them unclosed.

Void elements

HTML void elements do not need to be self-closed:

<input type="text">
<br>
<img src="image.png">

Element-specific parsing rules

Certain HTML elements have special parsing rules that differ from the general rules outlined above. These include:

  • <script> - contains JavaScript/TypeScript (see §5)
  • <style> - contains CSS (see §4)
Elements that disable expression parsing

The following elements disable expression parsing entirely. Inside these elements and their descendants, curly braces { and } are treated as literal text characters, not expression delimiters:

  • <math>
  • <iframe>
  • <noembed>
  • <noframes>
  • <plaintext>
  • <xmp>

Example with <math>:

<math xmlns="http://www.w3.org/1998/Math/MathML">
  <semantics>
    <annotation encoding="application/x-tex">
      f\colon X \to \mathbb{R}^{2x}
    </annotation>
  </semantics>
</math>

In this example, {R} and {2x} are treated as literal text, not expressions.

Raw text elements that still support expressions

The <title> and <textarea> elements have special parsing where HTML tags inside them are treated as literal text rather than elements, but expressions still work.

<title>{pageTitle} - Site with <b>bold</b></title>
<textarea>{defaultValue} with <div>tags</div></textarea>

In this example:

  • {pageTitle} and {defaultValue} are expressions that will be evaluated
  • The <b>bold</b> and <div>tags</div> are treated as literal text, not HTML elements

4. Style Blocks

<style>
  h1 { color: red; }
</style>

Multiple <style> blocks are allowed per file.

4.1 Language

By default, <style> blocks can contain CSS. The content adheres to standard CSS syntax as defined by the CSS Syntax Module.

4.2 lang attribute

Specifies a preprocessor language:

<style lang="scss">
  $accent: #1d4ed8;
  .card { border-color: $accent; }
</style>

The syntax then follows the rules of the specified preprocessor instead of standard CSS.


5. Script Blocks

<script>
  console.log("Hello");
</script>

Multiple <script> blocks are allowed per file.

5.1 Language

A bare <script> tag with no attributes can contain TypeScript. The content adheres to standard TypeScript syntax.

<script>
  interface User {
    id: number;
    name: string;
  }

  // ...
</script>

If any attributes are present, the content instead follows standard HTML <script> element rules.

<script defer>
	// JavaScript
</script>

<script type="module">
  // JavaScript module
  import { foo } from "./foo.js";
</script>

<script type="application/json">
  { "key": "value" }
</script>