komal.nakrani
Browse Themes
ThemesCategoriesDocsBlogTips & Tricks
Styling••By Komal Nakrani

Astro define:vars: Passing Values to CSS

Astro's define:vars passes frontmatter values into a style or script tag as CSS custom properties or variables. How it works, when to use it, and what it costs.

#Astro#CSS#Styling#Directives

Astro’s define:vars directive passes values from a component’s frontmatter into its <style> or <script> tag. On a style tag, each key becomes a CSS custom property you read with var(--key), scoped to that component instance. Use it when a style depends on a prop, such as a brand colour, a column count or a background image, without falling back to inline style attributes.

Key Takeaways

  • <style define:vars={{ accent }}> makes var(--accent) available in that component’s CSS.
  • Each instance gets its own values, so the same component can render in different colours.
  • On a <script>, define:vars makes the script is:inline: no bundling, no TypeScript, repeated per instance.
  • For scripts, pass data through data-* attributes instead.

Basic use

---
// src/components/Badge.astro
interface Props { color?: string; text?: string }
const { color = '#2563eb', text = 'white' } = Astro.props;
---
<span class="badge"><slot /></span>

<style define:vars={{ color, text }}>
  .badge {
    background: var(--color);
    color: var(--text);
    padding: 0.25rem 0.6rem;
    border-radius: 999px;
  }
</style>
<Badge>Default</Badge>
<Badge color="#16a34a">Live</Badge>
<Badge color="#dc2626">Deprecated</Badge>

Astro sets the variables in a style attribute on the component’s root elements, so each instance resolves var(--color) to its own value. The stylesheet itself is shared and cached.

Useful patterns

Layouts driven by props:

---
const { columns = 3, gap = '1.5rem' } = Astro.props;
---
<div class="grid"><slot /></div>

<style define:vars={{ columns, gap }}>
  .grid {
    display: grid;
    grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
    gap: var(--gap);
  }
</style>

Background images from content:

---
import { getImage } from 'astro:assets';
import heroSrc from '../assets/hero.jpg';
const hero = await getImage({ src: heroSrc, width: 1600, format: 'webp' });
const bg = `url(${hero.src})`;
---
<section class="hero"><slot /></section>

<style define:vars={{ bg }}>
  .hero { background-image: var(--bg); background-size: cover; }
</style>

Keys must be valid as custom property names; camelCase works, and Astro uses the name as written.

On scripts: the catch

define:vars also works on <script>, but it changes how the script is handled:

<script define:vars={{ apiUrl }}>
  console.log(apiUrl); // works, but this script is now inline
</script>

A script with define:vars is implicitly is:inline. That means it isn’t bundled, imports and TypeScript don’t work, and it’s repeated in the HTML for every instance of the component. For anything beyond a line or two, pass data through attributes and keep a normal processed script:

---
const { apiUrl } = Astro.props;
---
<div class="widget" data-api-url={apiUrl}></div>

<script>
  document.querySelectorAll<HTMLElement>('.widget').forEach((el) => {
    const apiUrl = el.dataset.apiUrl;
    // ...
  });
</script>

When not to use it

  • Values that never change per instance. Put them in a global token file as plain CSS custom properties. See the token section in the Astro styling guide.
  • Tailwind projects. Arbitrary values (bg-(--accent) in Tailwind 4, bg-[var(--accent)] in Tailwind 3) plus a style attribute on the element often read more clearly.
  • Client-side changes. define:vars is set at render time. To change a value in the browser, set the custom property from a script with el.style.setProperty('--color', value).

For a gentler alternative in Sass projects, see using Sass in Astro. The styling docs for Komal themes show where theme tokens live, which covers most cases where you might otherwise reach for define:vars. The Astro directives reference has the formal definition.

FAQ

Does define:vars work with scoped styles?

Yes. The CSS stays scoped to the component, and the variables are set per instance.

Why can’t I import modules in a script with define:vars?

Because define:vars makes the script inline and unprocessed. Use data-* attributes and a regular <script> instead.