@lpdsgn/astro-themes
v0.1.3
Published
Perfect dark mode in Astro with no flash. System preference, multiple themes, and sync across tabs.
Maintainers
Readme
astro-themes
Perfect dark mode in Astro with no flash. An Astro integration that mirrors the behavior of next-themes.
Features
- ✅ Perfect dark mode in 2 lines of code
- ✅ No flash on load (SSR and SSG)
- ✅ System preference with
prefers-color-scheme - ✅ Themed browser UI with
color-scheme - ✅ Sync theme across tabs and windows
- ✅ Force pages to specific themes
- ✅ Class or data attribute selector
- ✅ TypeScript support
Quick Start
1. Install
pnpm astro add @lpdsgn/astro-themesnpx astro add @lpdsgn/astro-themesyarn astro add @lpdsgn/astro-themes2. Add ThemeProvider
---
import { ThemeProvider } from "@lpdsgn/astro-themes/components";
---
<!doctype html>
<html lang="en">
<head>
<ThemeProvider />
</head>
<body>
<slot />
</body>
</html>That's it! Your Astro app now supports dark mode with system preference detection and cross-tab sync.
Styling
CSS Variables
By default, @lpdsgn/astro-themes sets data-theme on the <html> element:
:root {
--background: white;
--foreground: black;
}
[data-theme='dark'] {
--background: black;
--foreground: white;
}TailwindCSS
For Tailwind's class-based dark mode, set attribute="class":
<ThemeProvider attribute="class" />Then configure Tailwind:
Tailwind v4:
@import 'tailwindcss';
@custom-variant dark (&:is(.dark *));Tailwind v3:
module.exports = {
darkMode: 'selector',
}Now use dark-mode classes:
<h1 class="text-black dark:text-white">Hello</h1>Changing the Theme
Using Client Helpers (Recommended)
<script>
import { setTheme, toggleTheme, onThemeChange, getResolvedTheme } from '@lpdsgn/astro-themes';
// Toggle between light and dark
toggleTheme();
// Set a specific theme
setTheme('dark');
// Get current resolved theme
const current = getResolvedTheme(); // 'light' | 'dark'
// Listen to theme changes
const unsubscribe = onThemeChange(({ theme, resolvedTheme }) => {
console.log('Theme changed:', resolvedTheme);
});
</script>Using the Global Object
<button id="theme-toggle">Toggle</button>
<script>
document.getElementById('theme-toggle').addEventListener('click', () => {
const { resolvedTheme, setTheme } = window.__ASTRO_THEMES__;
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
});
</script>Configuration
ThemeProvider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| storageKey | string | 'theme' | localStorage key for theme setting |
| defaultTheme | string | 'system' | Default theme ('light', 'dark', or 'system') |
| themes | string[] | ['light', 'dark'] | Available theme names |
| attribute | string \| string[] | 'data-theme' | HTML attribute to set. Use 'class' for Tailwind |
| value | object | - | Map theme names to custom attribute values |
| forcedTheme | string | - | Force a specific theme on this page |
| enableSystem | boolean | true | Enable system preference detection |
| enableColorScheme | boolean | true | Set color-scheme CSS property |
| disableTransitionOnChange | boolean | false | Disable transitions when switching |
| nonce | string | - | CSP nonce for the script tag |
| scriptProps | object | - | Additional props for the script tag |
Integration Options
The integration can be configured in your astro.config.mjs:
import { defineConfig } from "astro/config";
import astroThemes from "@lpdsgn/astro-themes";
export default defineConfig({
integrations: [
astroThemes({
devToolbar: true, // Enable Dev Toolbar App (default: true)
}),
],
});| Option | Type | Default | Description |
|--------|------|---------|-------------|
| devToolbar | boolean | true | Enable the Dev Toolbar App for theme switching during development |
Examples
Force a Page Theme
---
import { ThemeProvider } from "@lpdsgn/astro-themes/components";
import Layout from "../layouts/Layout.astro";
---
<Layout>
<ThemeProvider forcedTheme="dark" />
<!-- This page is always dark -->
</Layout>Multiple Themes
<ThemeProvider themes={['light', 'dark', 'purple', 'pink']} />Custom Attribute Values
<ThemeProvider
attribute="data-theme"
value={{ light: 'light-mode', dark: 'dark-mode' }}
/>Disable Transitions on Change
<ThemeProvider disableTransitionOnChange />Cloudflare Rocket Loader
<ThemeProvider scriptProps={{ 'data-cfasync': 'false' }} />API Reference
Client Helpers
Import from @lpdsgn/astro-themes:
| Function | Description |
|----------|-------------|
| getTheme() | Get complete theme state object |
| setTheme(theme) | Set theme (string or callback function) |
| toggleTheme() | Toggle between light and dark |
| getResolvedTheme() | Get resolved theme ('light' or 'dark') |
| getSystemTheme() | Get system preference |
| getThemes() | Get list of available themes |
| isForcedTheme() | Check if current page has forced theme |
| onThemeChange(callback) | Subscribe to changes (returns unsubscribe) |
Theme State Object
Available via window.__ASTRO_THEMES__:
| Property | Type | Description |
|----------|------|-------------|
| theme | string | Current theme name |
| resolvedTheme | string | Resolved theme ('light' or 'dark') |
| systemTheme | 'light' \| 'dark' | System preference |
| forcedTheme | string \| undefined | Forced theme if set |
| themes | string[] | Available themes |
| setTheme(theme) | function | Update the theme |
Avoiding Hydration Mismatch
Since the theme is unknown on the server, use CSS to conditionally show content:
[data-theme='dark'] .light-only { display: none; }
[data-theme='light'] .dark-only { display: none; }Or check in client-side scripts:
<script>
if (window.__ASTRO_THEMES__) {
const { resolvedTheme } = window.__ASTRO_THEMES__;
// Update UI based on theme
}
</script>Contributing
This package is structured as a monorepo:
playground– Development testing environmentpackages/astro-themes– The integration package
pnpm i --frozen-lockfile
pnpm devLicense
MIT Licensed. Made with ❤️
Acknowledgements
- Inspired by next-themes by Paco Coursey
- Built with astro-integration-kit by Florian Lefebvre
