Conditional rendering in Astro works the same way as in JSX: write a JavaScript expression inside curly braces in the template. Use {condition && <Element />} to show something only when a condition is true, and {condition ? <A /> : <B />} to choose between two things. There is no v-if or {#if} block syntax.
Key Takeaways
&&shows or hides; a ternary chooses between two branches.- Move complex logic into the frontmatter and render a variable.
- Watch out for
0 && ..., which renders a literal0.- Conditions run on the server, at build or request time, not in the browser.
Show or hide with &&
---
const { title, subtitle, isNew = false } = Astro.props;
---
<h2>
{title}
{isNew && <span class="badge">New</span>}
</h2>
{subtitle && <p class="subtitle">{subtitle}</p>}
If subtitle is an empty string or undefined, nothing renders.
The zero trap
&& returns its left side when that side is falsy. For 0, that means a 0 appears on the page:
{/* Renders "0" when there are no comments */}
{comments.length && <p>{comments.length} comments</p>}
{/* Correct */}
{comments.length > 0 && <p>{comments.length} comments</p>}
Always compare numbers explicitly.
Either/or with a ternary
---
const user = Astro.locals.user;
---
{user
? <a href="/account/">Hi, {user.name}</a>
: <a href="/login/">Sign in</a>}
Nested ternaries get hard to read quickly. Past two branches, use the next pattern.
Complex conditions in the frontmatter
Decide in the script, render a single value in the template:
---
import InfoIcon from './icons/Info.astro';
import WarningIcon from './icons/Warning.astro';
import ErrorIcon from './icons/Error.astro';
interface Props { type: 'info' | 'warning' | 'error' }
const { type } = Astro.props;
const icons = { info: InfoIcon, warning: WarningIcon, error: ErrorIcon };
const Icon = icons[type];
---
<div class:list={['alert', `alert-${type}`]}>
<Icon />
<slot />
</div>
Assigning a component to a capitalised variable and rendering <Icon /> is a dynamic component. It avoids a chain of conditions in the template entirely.
Dynamic tags
The same trick works for HTML elements. A heading component that renders the right level:
---
interface Props { level?: 1 | 2 | 3 | 4 }
const { level = 2 } = Astro.props;
const Tag = `h${level}`;
---
<Tag class="heading"><slot /></Tag>
Note that a dynamic tag variable can’t be used for a component with a client:* directive; hydration needs a static import.
Conditional attributes and classes
Attributes set to false, null or undefined are omitted:
<a href={href} target={external ? '_blank' : undefined} rel={external ? 'noopener' : undefined}>
<slot />
</a>
For classes, class:list handles conditions cleanly (the Astro styling guide has more on combining it with Tailwind):
<li class:list={['nav-item', { active: Astro.url.pathname === href }]}>
Conditional slots
Render a wrapper only when a slot was filled:
{Astro.slots.has('footer') && (
<footer class="card-footer"><slot name="footer" /></footer>
)}
Without the check, you’d get an empty <footer> element with its padding and border.
Remember: this runs on the server
Template conditions are evaluated when the page renders, at build time for static pages. A condition based on window.innerWidth or localStorage won’t work here; that belongs in a <script> tag or an island. For responsive show/hide, CSS media queries are usually the right tool.
For props, slots and dynamic components in depth, read the Astro components guide. If you need to render a raw HTML string conditionally, see Fragment and set:html. The Astro template syntax reference has the full rules.
FAQ
Does Astro have an if statement in templates?
No. Use JavaScript expressions: && for show/hide and the ternary operator for either/or. For complex logic, compute a value in the frontmatter.
Why does my Astro template show a 0?
You used {count && ...} with count equal to 0. Write {count > 0 && ...} instead.