CSS Variables and class:list
React developers routinely thread dynamic values into styles with inline style objects (style={{ color: accentColor }}) or with clsx for conditional class names. Astro gives you two dedicated built-in tools for the same jobs: define:vars bridges frontmatter values into CSS custom properties, and class:list replaces clsx for conditional class toggling.
define:vars — frontmatter values in CSS
Section titled “define:vars — frontmatter values in CSS”define:vars is a <style> directive that maps a JS object of variables to CSS custom properties on the element. You write the values in the frontmatter and reference them as var(--name) in CSS.
// Badge.tsxexport default function Badge({ label, color = "#6366f1", bg = "#eef2ff",}: { label: string; color?: string; bg?: string;}) { return ( <span style={{ color, backgroundColor: bg, borderColor: color }} className="badge" > {label} </span> );}---// Badge.astrointerface Props { label: string; color?: string; bg?: string; }const { label, color = "#6366f1", bg = "#eef2ff" } = Astro.props;---<span class="badge">{label}</span>
<style define:vars={{ color, bg }}> .badge { color: var(--color); background: var(--bg); border: 1px solid var(--color); padding: .2em .7em; border-radius: 999px; font-size: .8rem; font-weight: 600; display: inline-block; }</style>The compiled output sets the variables as inline custom properties on the element’s style attribute — so they cascade naturally — while the actual style rules stay in the <style> block and benefit from scoping.
class:list — conditional class names
Section titled “class:list — conditional class names”class:list is the Astro equivalent of clsx. It accepts an array whose items can be strings (always applied), objects with boolean values (applied when the value is truthy), or other arrays.
// Button.tsximport clsx from 'clsx';import styles from './Button.module.css';
type Variant = 'primary' | 'ghost' | 'danger';export default function Button({ label, variant = 'primary', disabled = false,}: { label: string; variant?: Variant; disabled?: boolean;}) { return ( <button className={clsx( styles.btn, styles[variant], { [styles.disabled]: disabled }, )} disabled={disabled} > {label} </button> );}---// Button.astrotype Variant = 'primary' | 'ghost' | 'danger';interface Props { label: string; variant?: Variant; disabled?: boolean; }const { label, variant = 'primary', disabled = false } = Astro.props;---<button class:list={["btn", variant, { disabled }]} disabled={disabled}> {label}</button>
<style> .btn { border-radius: 6px; padding: .5rem 1.2rem; border: none; cursor: pointer; font-size: .95rem; } .primary { background: #6366f1; color: #fff; } .ghost { background: transparent; border: 1px solid #6366f1; color: #6366f1; } .danger { background: #ef4444; color: #fff; } .disabled { opacity: .45; cursor: not-allowed; }</style>class:list accepts the same shapes as clsx: plain strings, { className: booleanExpr } objects, and nested arrays. Falsy items are silently ignored.
Combining both
Section titled “Combining both”define:vars and class:list compose naturally when you need both dynamic color values and conditional classes.
Runnable example
Section titled “Runnable example”---
const theme = "ocean";
const themes = {
ocean: { bg: "#0ea5e9", text: "#fff", accent: "#0284c7" },
forest: { bg: "#22c55e", text: "#fff", accent: "#16a34a" },
sunset: { bg: "#f97316", text: "#fff", accent: "#ea580c" },
};
const { bg, text, accent } = themes[theme];
const cards = [
{ title: "Scoped styles", active: true },
{ title: "Global styles", active: false },
{ title: "define:vars", active: true },
{ title: "class:list", active: false },
];
---
<html lang="en">
<head><meta charset="utf-8" /><title>define:vars + class:list</title></head>
<body>
<header class="hero">
<h1>CSS Variables + class:list</h1>
<p>Theme: <strong>{theme}</strong></p>
</header>
<ul class="grid">
{cards.map(card => (
<li class:list={["card", { active: card.active }]}>
{card.title}
</li>
))}
</ul>
</body>
</html>
<style define:vars={{ bg, text, accent }}>
body { font-family: sans-serif; margin: 0; background: #f1f5f9; }
.hero { background: var(--bg); color: var(--text); padding: 2rem; }
.hero h1 { margin: 0 0 .25rem; }
.hero p { margin: 0; opacity: .85; }
.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: .75rem; padding: 1.5rem; list-style: none; margin: 0; }
.card { background: #fff; border: 2px solid #e2e8f0; border-radius: 8px; padding: 1rem; font-size: .95rem; color: #475569; }
.active { border-color: var(--accent); color: var(--accent); font-weight: 700; }
</style>