Skip to content
Astro as templating 4 min read

Conditional Rendering

Astro templates have no special if syntax. Because the markup is HTML interleaved with JavaScript expressions, you decide what to render the same way you’d compute any value: with ternaries (cond ? a : b) and logical short-circuits (cond && a). This keeps templates declarative, leans on the fact that false, null, and undefined render nothing, and — like everything in Astro — runs on the server so the discarded branches never ship to the browser.

Ternaries for either/or

A ternary is the right tool when you have two mutually exclusive outputs. Place it inside an expression slot and it resolves to one branch or the other.

---
const user = { name: "Grace", premium: true };
---
<p>Plan: {user.premium ? "Premium" : "Free"}</p>
<span class={user.premium ? "badge gold" : "badge"}>
  {user.premium ? "★ Member" : "Guest"}
</span>

Output:

<p>Plan: Premium</p>
<span class="badge gold">★ Member</span>

Ternaries also work for choosing between whole elements or components, not just text. Wrap each branch in parentheses for readability:

---
import LoginForm from "../components/LoginForm.astro";
import Dashboard from "../components/Dashboard.astro";

const isLoggedIn = Astro.cookies.has("session");
---
{isLoggedIn ? (
  <Dashboard />
) : (
  <LoginForm />
)}

Logical AND for optional output

When there is no “else” branch — you either render something or render nothing — use the && short-circuit. If the left side is truthy, the right side renders; if it’s falsy, the expression evaluates to the falsy value, which Astro renders as nothing.

---
const errors = ["Email is required"];
const user = { name: "Ada", avatar: null };
---
{errors.length > 0 && (
  <ul class="errors">
    {errors.map((e) => <li>{e}</li>)}
  </ul>
)}

{user.avatar && <img src={user.avatar} alt={user.name} />}

Because user.avatar is null, the <img> is skipped entirely — no broken image, no empty tag.

Gotcha: && short-circuits to its left operand when that operand is falsy. A numeric 0 is falsy, so {count && <Badge count={count} />} renders a literal 0 when count is zero. Guard with an explicit comparison instead: {count > 0 && <Badge count={count} />}.

Choosing between the operators

PatternUse whenExample
cond ? a : bTwo outcomes (either/or){ok ? <Pass /> : <Fail />}
cond && aOne outcome or nothing{isNew && <NewTag />}
cond || fallbackA value with a default{title || "Untitled"}
cond ?? fallbackDefault only for null/undefined{count ?? 0}

The nullish coalescing operator ?? is the safe choice for defaults when 0, "", or false are valid values you want to keep, since it only falls back on null or undefined.

Compute conditions in the script

Inline ternaries are great for a single decision, but nested ternaries become unreadable fast. When a choice has more than two outcomes, or the condition is non-trivial, compute the result in the component script and interpolate the variable. The script is plain JavaScript, so if/else, switch, and early returns are all available.

---
type Status = "draft" | "published" | "archived";
const status: Status = "published";

let label: string;
let tone: string;
if (status === "published") {
  label = "Live";
  tone = "ok";
} else if (status === "draft") {
  label = "In progress";
  tone = "warn";
} else {
  label = "Archived";
  tone = "muted";
}
---
<span class={`status status--${tone}`}>{label}</span>

Output:

<span class="status status--ok">Live</span>

You can also hoist a whole block of markup into a variable when a branch is large, keeping the template skimmable:

---
const items = ["First", "Second"];

const list =
  items.length > 0 ? (
    <ul>{items.map((i) => <li>{i}</li>)}</ul>
  ) : (
    <p class="empty">Nothing here yet.</p>
  );
---
<section>{list}</section>

Rendering nothing on purpose

Sometimes the right answer is to render an empty fragment. null, false, and undefined all produce no output, so an explicit null branch in a ternary is the idiomatic “render nothing” signal:

---
const notice: string | null = null;
---
{notice ? <aside class="notice">{notice}</aside> : null}

This pairs naturally with optional props and content collection entries, where a field may be absent.

Best Practices

  • Reach for && when there’s no else branch, and a ternary when there are exactly two outcomes.
  • Guard && against falsy 0/"" by comparing explicitly (count > 0 &&) so you don’t leak a literal 0 into the DOM.
  • Prefer ?? over || for defaults when 0, empty string, or false are legitimate values.
  • Move anything beyond a single decision into the component script with if/switch — never nest ternaries more than one level deep.
  • Hoist large conditional branches into a named variable so the template stays declarative and scannable.
  • Remember conditions run on the server: untaken branches and their imports are never sent to the client, preserving zero-JS-by-default.
Last updated June 14, 2026
Was this helpful?