Dynamic Attributes
In Astro, any HTML attribute can be driven by a JavaScript expression evaluated at build time (or on the server). You write the attribute value inside curly braces, and Astro renders the resolved string into the final HTML. Because this happens during rendering rather than in the browser, dynamic attributes ship zero JavaScript by default — the page arrives fully formed. This makes attribute binding one of the most common and powerful patterns when authoring .astro components.
Binding expressions to attributes
To make an attribute dynamic, replace its quoted value with a {expression}. The expression has access to any variable, prop, or function defined in the component script (the --- fence at the top of the file).
---
interface Props {
href: string;
label: string;
external?: boolean;
}
const { href, label, external = false } = Astro.props;
const target = external ? "_blank" : "_self";
---
<a href={href} target={target} title={`Go to ${label}`}>
{label}
</a>
You can bind any attribute this way — src, alt, data-*, aria-*, id, and custom attributes all behave identically. Template literals are handy when you need to interpolate a value into a larger string, as shown with title above.
Note: Astro evaluates these expressions on the server or at build time, not in the browser. If you need attributes that change in response to user interaction, reach for a framework component with a
client:*directive or a small<script>.
Boolean attributes
HTML boolean attributes — such as disabled, checked, required, readonly, hidden, and selected — are special: their mere presence means “true”, regardless of value. Astro handles this correctly. When you bind a boolean expression, Astro renders the attribute only when the value is truthy and omits it entirely when falsy.
---
const { isSubmitting, agreed } = Astro.props;
---
<button disabled={isSubmitting}>Save</button>
<input type="checkbox" checked={agreed} />
If isSubmitting is false, the rendered output is simply <button>Save</button> with no disabled attribute at all.
Output:
<button disabled>Save</button>
<input type="checkbox">
This is exactly the semantics you want — there is no need to write disabled={isSubmitting ? "disabled" : undefined} by hand. Note that passing undefined or null to any attribute (boolean or not) also causes Astro to drop it from the output, which is a clean way to conditionally include an attribute.
The class attribute
The class attribute works like any other dynamic attribute — you can bind a string expression directly. Note that unlike some frameworks, Astro uses class, not className, even inside .astro files.
---
const { active } = Astro.props;
const classes = `card ${active ? "card--active" : ""}`;
---
<div class={classes}>...</div>
For more ergonomic conditional classes, Astro provides a built-in class:list directive that accepts arrays, objects, and conditionals, then normalizes them into a clean, deduplicated string.
---
const { active, size } = Astro.props;
---
<div
class:list={[
"card",
{ "card--active": active },
size === "lg" && "card--large",
]}
>
Content
</div>
Object keys are included when their value is truthy; falsy values, false, null, and undefined entries are skipped automatically. If both class and class:list are present on the same element, Astro merges them.
| Approach | Best for | Output handling |
|---|---|---|
class={string} | Simple, fully computed strings | Rendered verbatim |
class:list={[...]} | Conditional/toggled classes | Filtered, deduplicated, joined |
The style attribute
Like class, style accepts a string, but it can also accept an object of CSS properties. When you pass an object, Astro serializes it into a valid inline style string. Use camelCase or kebab-case keys — both are accepted.
---
const { color, columns } = Astro.props;
---
<p style={{ color: color, fontWeight: "bold" }}>Tinted text</p>
<div style={{ "--cols": columns }}>Grid using a CSS variable</div>
Output:
<p style="color:teal;font-weight:bold;">Tinted text</p>
<div style="--cols:3;">Grid using a CSS variable</div>
CSS custom properties (--cols) are a great way to pass dynamic values from your component into scoped CSS without inlining a full ruleset.
Spreading attributes
When you have many attributes — or want to forward arbitrary props to an element — use the spread operator. This is common for wrapper components that pass through native HTML attributes.
---
const { class: className, ...rest } = Astro.props;
---
<input class:list={["field", className]} {...rest} />
Here every prop other than class is spread directly onto the <input>, so callers can set type, placeholder, name, required, and so on without you declaring each one.
Best practices
- Prefer
undefined/nullover empty strings when you want an attribute omitted — Astro drops it cleanly. - Use
class:listinstead of hand-built ternary strings for conditional classes; it deduplicates and filters falsy values for you. - Pass dynamic values into scoped styles via CSS custom properties rather than large inline
stylestrings. - Remember dynamic attributes resolve at build/server time — use
client:*islands or<script>for runtime-reactive attributes. - Use attribute spreading (
{...rest}) to forward native HTML attributes in reusable wrapper components. - Always use
class(notclassName) in.astrofiles to avoid silent mistakes.