CSS Variables และ class:list
นักพัฒนา React มักนำค่า dynamic เข้า style ด้วย inline style object (style={{ color: accentColor }}) หรือใช้ clsx สำหรับ conditional class names Astro มีเครื่องมือ built-in สองตัวสำหรับงานเดียวกัน: define:vars เชื่อมค่า frontmatter เข้า CSS custom properties และ class:list แทนที่ clsx สำหรับการสลับ class
define:vars — ค่า frontmatter ใน CSS
หัวข้อที่มีชื่อว่า “define:vars — ค่า frontmatter ใน CSS”define:vars คือ directive ของ <style> ที่ map JS object ของตัวแปรไปเป็น CSS custom properties บน element คุณเขียนค่าใน frontmatter แล้วอ้างถึงด้วย var(--name) ใน 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>output ที่ compile แล้วจะตั้งค่า variable เหล่านั้นเป็น inline custom properties บน style attribute ของ element — ดังนั้นจึง cascade ตามปกติ — ส่วน style rules จริงยังคงอยู่ใน <style> block และได้ประโยชน์จาก scoping
class:list — conditional class names
หัวข้อที่มีชื่อว่า “class:list — conditional class names”class:list คือ Astro equivalent ของ clsx รับ array ที่ item แต่ละตัวเป็น string (ใช้เสมอ), object ที่มีค่า boolean (ใช้เมื่อค่าเป็น truthy) หรือ array ซ้อนกัน
// 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 รับ shape เดียวกับ clsx: plain strings, object { className: booleanExpr } และ nested arrays item ที่เป็น falsy จะถูกละเว้น
ใช้ทั้งคู่ร่วมกัน
หัวข้อที่มีชื่อว่า “ใช้ทั้งคู่ร่วมกัน”define:vars และ class:list ทำงานร่วมกันได้ดี เมื่อคุณต้องการทั้งค่าสี dynamic และ conditional classes
ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”---
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>