Templating Syntax
Astro’s templating language is, at its core, just HTML — but with a small set of JSX-inspired superpowers layered on top. Anything you write below the --- component-script fence is a template: valid HTML that can interpolate JavaScript values, react to data, spread attributes, and apply directives. Because Astro renders this template to static HTML on the server, the syntax feels familiar to anyone coming from React or Handlebars, yet it ships zero client JavaScript unless you explicitly opt in. Understanding the template syntax is the foundation for everything else you build with Astro.
The component script and the template
Every .astro file is split into two parts. The component script between the --- fences runs only at build time (or on the server) and never reaches the browser. Everything after the closing fence is the template — HTML markup that can reference any variable defined in the script.
---
// src/components/Greeting.astro — this runs on the server
const name = "Ada";
const tasks = ["Write docs", "Ship build", "Sleep"];
const now = new Date().toLocaleDateString("en-US");
---
<section>
<h1>Hello, {name}!</h1>
<p>Today is {now}. You have {tasks.length} tasks.</p>
</section>
The value inside { } is a real JavaScript expression, evaluated once during rendering. The result is serialized to HTML, so the visitor only ever sees the finished markup.
Dynamic expressions
Curly braces accept any JavaScript expression, not just variable names. You can call functions, do arithmetic, format dates, or map over arrays. Because Astro renders on the server, expressions are evaluated eagerly and can even be asynchronous when used with top-level await in the script.
---
const price = 42.5;
const items = ["espresso", "latte", "cortado"];
---
<p>Total with tax: ${(price * 1.2).toFixed(2)}</p>
<ul>
{items.map((item) => <li>{item.toUpperCase()}</li>)}
</ul>
Unlike Handlebars, where logic lives in helpers and partials, Astro lets you write plain JavaScript inline — there is no separate templating dialect to learn. Unlike JSX, Astro returns HTML strings rather than a virtual DOM, so the markup you write is closer to the markup that ships.
Tip: Expressions are for values, not statements. You can use
&&, ternaries, and.map()to produce output, but you cannot placeifblocks orforloops directly inside{ }. Compute those in the script or use the expression forms instead.
Attribute handling
Attributes work just like HTML, but their values can be dynamic expressions. Astro is smart about how it serializes different value types, which removes a whole class of JSX papercuts.
---
const url = "/about";
const isActive = true;
const widths = [400, 800];
---
<a href={url} aria-current={isActive ? "page" : undefined}>About</a>
<img src="/hero.jpg" sizes={widths.map((w) => `${w}px`).join(", ")} />
| Behavior | Astro | JSX |
|---|---|---|
| Attribute names | Standard HTML (class, for) | camelCase (className, htmlFor) |
| Boolean attrs | Omitted when falsy | Omitted when falsy |
undefined/null values | Attribute removed entirely | Attribute removed entirely |
| Output | HTML string | Virtual DOM nodes |
A key convenience: when an attribute resolves to false, null, or undefined, Astro drops it from the output entirely, so aria-current={undefined} simply disappears.
Spreading and shorthand attributes
You can forward an object of props onto an element with the spread operator, and use the shorthand when a variable name matches the attribute name.
---
const attrs = { class: "btn", "data-variant": "primary" };
const id = "submit";
---
<button {...attrs} {id}>Save</button>
This renders <button class="btn" data-variant="primary" id="submit">Save</button>.
Comparison with JSX and Handlebars
Astro borrows the ergonomics of both worlds while staying HTML-first.
| Feature | Astro | JSX (React) | Handlebars |
|---|---|---|---|
| Expression syntax | { js } | { js } | {{ value }} |
| Logic location | Inline JS + script | Inline JS | Helpers/partials |
| Attribute casing | HTML (class) | camelCase | HTML |
| Conditionals | && / ternary | && / ternary | {{#if}} |
| Output | Static HTML | Virtual DOM | Compiled HTML |
| Client JS shipped | None by default | Always | None |
Best practices
- Keep heavy logic — fetches, sorting, formatting — in the component script and feed clean values into the template.
- Prefer ternaries and
&&for inline conditionals; reach for early-computed variables when an expression gets long. - Let Astro drop falsy attributes for you instead of building strings conditionally.
- Use the
{...spread}and{shorthand}forms to keep prop forwarding tidy. - Remember that template expressions run on the server — add a
client:*directive only on the islands that truly need interactivity. - Use standard HTML attribute names (
class,for) rather than the camelCase JSX equivalents.