Skip to content
Astro as templating 4 min read

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 place if blocks or for loops 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(", ")} />
BehaviorAstroJSX
Attribute namesStandard HTML (class, for)camelCase (className, htmlFor)
Boolean attrsOmitted when falsyOmitted when falsy
undefined/null valuesAttribute removed entirelyAttribute removed entirely
OutputHTML stringVirtual 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.

FeatureAstroJSX (React)Handlebars
Expression syntax{ js }{ js }{{ value }}
Logic locationInline JS + scriptInline JSHelpers/partials
Attribute casingHTML (class)camelCaseHTML
Conditionals&& / ternary&& / ternary{{#if}}
OutputStatic HTMLVirtual DOMCompiled HTML
Client JS shippedNone by defaultAlwaysNone

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.
Last updated June 14, 2026
Was this helpful?