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 numeric0is falsy, so{count && <Badge count={count} />}renders a literal0whencountis zero. Guard with an explicit comparison instead:{count > 0 && <Badge count={count} />}.
Choosing between the operators
| Pattern | Use when | Example |
|---|---|---|
cond ? a : b | Two outcomes (either/or) | {ok ? <Pass /> : <Fail />} |
cond && a | One outcome or nothing | {isNew && <NewTag />} |
cond || fallback | A value with a default | {title || "Untitled"} |
cond ?? fallback | Default 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 falsy0/""by comparing explicitly (count > 0 &&) so you don’t leak a literal0into the DOM. - Prefer
??over||for defaults when0, empty string, orfalseare 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.