Version: 1.0 Status: Draft Date: 2026-02-03
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 │
└──────────────────────────────────┘
<!-- A comment! -->
<h1>Hello, World!</h1>---
const greeting = "Hello";
---
<h1>{greeting}, World!</h1>
<style>
h1 { color: royalblue; }
</style>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.
return may be used at the top level:
---
import { getUser } from "../lib/auth.js";
const user = await getUser();
if (!user) {
return Astro.redirect("/login");
}
---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.
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>Unless mentioned otherwise, these differences apply both within the template and within expressions inside the template.
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.
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>The HTML doctype declaration is allowed.
<!DOCTYPE html>Top-level text inside the template is treated as text nodes.
Hello, World!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.
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 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" />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.
Attributes can use a shorthand syntax where {prop} is equivalent to prop={prop}:
<Component {prop} />
<!-- equivalent to: -->
<Component prop={prop} />Attributes can use backticks for interpolation without opening an expression:
<Component attribute=`hello ${value}` />Empty expressions {} inside attributes are allowed:
<Component attribute={} />Comments are allowed inside the opening tag of an element:
<div
{/* Hello */}
class="my-class"
></div>Less-than signs < in text nodes are parsed following HTML rules, meaning they do not need to be escaped:
<p>5 < 10</p>Tag names must use ASCII characters only. Non-ASCII tag names (e.g. <日本>) are not supported and are treated as text nodes.
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 />Like HTML, tags do not need to be explicitly closed.
<p>Hello
<p>WorldIt is up to the parser to optionally try to infer where tags close based on HTML parsing rules, or leave them unclosed.
HTML void elements do not need to be self-closed:
<input type="text">
<br>
<img src="image.png">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)
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.
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
<style>
h1 { color: red; }
</style>Multiple <style> blocks are allowed per file.
By default, <style> blocks can contain CSS. The content adheres to standard CSS syntax as defined by the CSS Syntax Module.
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.
<script>
console.log("Hello");
</script>Multiple <script> blocks are allowed per file.
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>