Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BankkRoll/tweakcn-theme-picker/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
Before installing themes, ensure you have shadcn/ui set up in your Astro project with React integration. If you haven’t already:
Install required components
The theme system requires these shadcn/ui components:npx shadcn@latest add dropdown-menu button
Installation
Install the theme system
Install the complete theme system with all 40+ themes:npx shadcn@latest add https://tweakcn-picker.vercel.app/r/astro/theme-system
This installs:
lib/themes-config.ts - Theme configuration and metadata
components/theme-script.astro - Inline script for preventing FOUC
components/mode-toggle.tsx - React component for theme switching
styles/themes/*.css - All 40+ theme CSS files
Add theme script to your layout
Add the theme script component in the <head> of your base layout to prevent flash of unstyled content (FOUC):---
import ThemeScript from "@/components/theme-script.astro";
import "@/styles/themes/index.css";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>My Astro Site</title>
<ThemeScript />
</head>
<body>
<slot />
</body>
</html>
The ThemeScript component must be placed in the <head> tag to work correctly and prevent FOUC.
Add the ModeToggle to your UI
Import and use the ModeToggle component in any .astro or .tsx file:src/components/Header.astro
---
import { ModeToggle } from "@/components/mode-toggle";
---
<header>
<nav>
<!-- Your navigation -->
<ModeToggle client:load />
</nav>
</header>
The client:load directive is required for React components to be interactive in Astro.
How it works
The Astro adapter uses an inline script for theme management to avoid hydration issues:
Theme Script implementation
Located at components/theme-script.astro:<script is:inline>
const STORAGE_KEY = "tweakcn-theme";
const DEFAULT_THEME = "default-dark";
function getThemePreference() {
if (typeof localStorage !== "undefined" && localStorage.getItem(STORAGE_KEY)) {
return localStorage.getItem(STORAGE_KEY);
}
return DEFAULT_THEME;
}
function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
}
// Apply theme immediately
const theme = getThemePreference();
applyTheme(theme);
// Listen for theme changes from React components
if (typeof window !== "undefined") {
window.setTheme = function(newTheme) {
localStorage.setItem(STORAGE_KEY, newTheme);
applyTheme(newTheme);
window.dispatchEvent(new CustomEvent("theme-change", { detail: newTheme }));
};
}
// Watch for storage changes (multi-tab sync)
window.addEventListener("storage", (e) => {
if (e.key === STORAGE_KEY && e.newValue) {
applyTheme(e.newValue);
}
});
</script>
The is:inline directive ensures the script runs before any other JavaScript, preventing FOUC. ModeToggle React component
Located at components/mode-toggle.tsx:import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";
const STORAGE_KEY = "tweakcn-theme";
const DEFAULT_THEME = "default-dark";
export function ModeToggle() {
const [theme, setThemeState] = React.useState<string>(DEFAULT_THEME);
React.useEffect(() => {
// Get initial theme
const currentTheme =
document.documentElement.getAttribute("data-theme") ||
localStorage.getItem(STORAGE_KEY) ||
DEFAULT_THEME;
setThemeState(currentTheme);
// Listen for theme changes
const handleThemeChange = (e: CustomEvent<string>) => {
setThemeState(e.detail);
};
window.addEventListener("theme-change", handleThemeChange as EventListener);
return () => {
window.removeEventListener("theme-change", handleThemeChange as EventListener);
};
}, []);
const setTheme = (newTheme: string) => {
setThemeState(newTheme);
if (typeof window !== "undefined" && window.setTheme) {
window.setTheme(newTheme);
}
};
const isDark = theme.endsWith("-dark");
const colorTheme = theme.replace("-light", "").replace("-dark", "");
return (
<Button
variant="outline"
size="icon"
onClick={() => setTheme(`${colorTheme}-${isDark ? "light" : "dark"}`)}
>
{isDark ? <Moon /> : <Sun />}
<span className="sr-only">Toggle theme</span>
</Button>
);
}
Multi-tab synchronization
The theme script includes multi-tab synchronization using the Storage API:window.addEventListener("storage", (e) => {
if (e.key === STORAGE_KEY && e.newValue) {
applyTheme(e.newValue);
}
});
When you change the theme in one tab, all other tabs automatically update.
Adding individual themes
To install specific themes instead of all 40+:
npx shadcn@latest add https://tweakcn-picker.vercel.app/r/theme-catppuccin
Then import only the themes you need:
src/styles/themes/index.css
@import "./catppuccin.css";
@import "./supabase.css";
@import "./nature.css";
Customizing themes
All theme CSS files are in src/styles/themes/. Each theme defines CSS variables:
src/styles/themes/supabase.css
[data-theme="supabase-light"] {
--background: oklch(0.99 0 0);
--foreground: oklch(0.25 0.02 267.08);
--primary: oklch(0.83 0.13 160.91);
--primary-foreground: oklch(0.20 0.03 267.08);
/* ... more variables */
}
[data-theme="supabase-dark"] {
--background: oklch(0.13 0.01 264.53);
--foreground: oklch(0.93 0.01 264.53);
--primary: oklch(0.44 0.1 156.76);
--primary-foreground: oklch(0.93 0.01 264.53);
/* ... more variables */
}
Edit these files to customize colors, borders, shadows, and more.
TypeScript usage
You can programmatically change themes using the global setTheme function:
// In any TypeScript file
if (typeof window !== "undefined" && window.setTheme) {
window.setTheme("supabase-dark");
}
Add TypeScript declarations for the global function:
/// <reference types="astro/client" />
declare global {
interface Window {
setTheme: (theme: string) => void;
}
}
export {};
Astro configuration
Ensure your astro.config.mjs includes the React integration:
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
integrations: [react()],
});
Client directives
When using React components in Astro, choose the appropriate client directive:
client:load - Hydrates immediately on page load (recommended for theme toggle)
client:idle - Hydrates when the main thread is free
client:visible - Hydrates when the component enters the viewport
<!-- Immediate hydration for theme toggle -->
<ModeToggle client:load />
<!-- Lazy hydration for less critical components -->
<ThemeShowcase client:visible />
Next steps
Browse Themes
Explore all 40+ available themes
Theme Picker
Preview themes in real-time
Next.js Setup
Install themes in Next.js
Vite Setup
Install themes in Vite React