Dynamic Theme Switching with CSS Variables and Tailwind
Published September 16th, 2025
Building flexible, themeable interfaces doesn’t have to be complex.
By combining Tailwind’s data attribute selectors with environment variables, you can create a clean system that switches themes dynamically without JavaScript or complex state management. Shout out to Matt Evans for teaching me this trick in a recent collaboration.
I’m using Astro below, but you can adapt this approach to any framework, heck you don’t even need a framework at all.
The Setup
Start by exposing your theme as a client-side environment variable in your Astro config:
// astro.config.mjs
export default defineConfig({
env: {
schema: {
THEME: envField.string({ context: "client", access: "public" }),
},
},
});
This allows you to control themes at the environment level—perfect for different deployments or runtime switching.
Layout Integration
In your layout, pass the theme to a data attribute on the body:
---
import { THEME } from "astro:env/client";
---
<body class="group/body" data-theme={THEME}>
<slot />
</body>
The group/body class is key—it creates a Tailwind group that child components can reference.
Theme-Aware Components
Now components can style themselves based on the active theme using Tailwind’s group-data-[] modifier:
<div class={`
border-gray-300
group-data-[theme=brutalist]/body:border-primary
group-data-[theme=round]/body:rounded-full
`}>
<input class="group-data-[theme=round]/body:px-4" />
</div>
Why This Works
It’s pretty simple:
- No JavaScript required: Themes switch purely through CSS selectors
- Environment flexibility: Set
THEME=brutalistorTHEME=roundat deploy time - Component isolation: Each component handles its own theme variations
- Performance: No runtime theme switching overhead
While we have a fixed theme set via environment variables, you can easily switch themes dynamically using JavaScript that updates the data-theme attribute on the body element.
CSS Variables Integration
For more complex theming, combine this approach with CSS custom properties:
[data-theme="brutalist"] {
--primary: #000;
--border-radius: 0;
}
[data-theme="round"] {
--primary: #3b82f6;
--border-radius: 9999px;
}
Then reference these in your Tailwind config or components as needed.
This pattern gives you the flexibility of environment-controlled theming with the power of Tailwind’s utility classes.