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 Remix project. 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/remix/theme-system
This installs:
lib/themes-config.ts - Theme configuration and metadata
lib/sessions.server.ts - Server-side session management
components/mode-toggle.tsx - Theme toggle component
app/routes/action.set-theme.ts - Theme action route
styles/themes/*.css - All 40+ theme CSS files
remix-themes dependency for SSR theme support
Configure theme session in root loader
Update your app/root.tsx to add the theme session resolver:import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/node";
import { themeSessionResolver } from "@/lib/sessions.server";
import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from "remix-themes";
import "@/styles/themes/index.css";
export async function loader({ request }: LoaderFunctionArgs) {
const { getTheme } = await themeSessionResolver(request);
return {
theme: getTheme(),
};
}
export default function AppWithProviders() {
const data = useLoaderData<typeof loader>();
return (
<ThemeProvider specifiedTheme={data.theme} themeAction="/action/set-theme">
<App />
</ThemeProvider>
);
}
function App() {
const data = useLoaderData<typeof loader>();
const [theme] = useTheme();
return (
<html lang="en" data-theme={theme ?? ""}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
Add the ModeToggle to your UI
Import and use the ModeToggle component anywhere in your app:app/components/Header.tsx
import { ModeToggle } from "@/components/mode-toggle";
export function Header() {
return (
<header>
<nav>
{/* Your navigation */}
<ModeToggle />
</nav>
</header>
);
}
Update session secret (production)
Replace the default session secret in lib/sessions.server.ts before deploying to production:
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "tweakcn-theme",
path: "/",
httpOnly: true,
sameSite: "lax",
secrets: [process.env.SESSION_SECRET || "s3cr3t"], // Use environment variable!
...(isProduction
? { domain: "your-production-domain.com", secure: true }
: {}),
},
});
Add SESSION_SECRET to your .env file:SESSION_SECRET=your-very-secret-random-string-here
How it works
The Remix adapter uses remix-themes for server-side rendering with cookie-based session storage:
Located at lib/sessions.server.ts:import { createCookieSessionStorage } from "@remix-run/node";
import { createThemeSessionResolver } from "remix-themes";
const isProduction = process.env.NODE_ENV === "production";
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "tweakcn-theme",
path: "/",
httpOnly: true,
sameSite: "lax",
secrets: ["s3cr3t"], // Replace in production!
...(isProduction
? { domain: "your-production-domain.com", secure: true }
: {}),
},
});
export const themeSessionResolver = createThemeSessionResolver(sessionStorage);
Themes are stored in HTTP-only cookies, making them available on the server for SSR.
Located at app/routes/action.set-theme.ts:import { createThemeAction } from "remix-themes";
import { themeSessionResolver } from "@/lib/sessions.server";
export const action = createThemeAction(themeSessionResolver);
This route handles theme changes from the client. The remix-themes package automatically creates the action handler.
Located at components/mode-toggle.tsx:import { Moon, Sun } from "lucide-react";
import { Theme, useTheme } from "remix-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ModeToggle() {
const [theme, setTheme] = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme(Theme.LIGHT)}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme(Theme.DARK)}>
Dark
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
Preventing flash of wrong theme
The PreventFlashOnWrongTheme component prevents FOUC by applying the theme before React hydrates:<head>
<PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
</head>
This component injects an inline script that runs immediately, setting the theme before any content renders.
Adding individual themes
To install specific themes instead of all 40+:
npx shadcn@latest add https://tweakcn-picker.vercel.app/r/theme-github
Then import only the themes you need:
app/styles/themes/index.css
@import "./github.css";
@import "./stripe.css";
@import "./spotify.css";
Customizing themes
All theme CSS files are in app/styles/themes/. Each theme defines CSS variables:
app/styles/themes/github.css
[data-theme="github-light"] {
--background: oklch(1 0 0);
--foreground: oklch(0.23 0.01 264.53);
--primary: oklch(0.52 0.16 145);
--primary-foreground: oklch(1 0 0);
/* ... more variables */
}
[data-theme="github-dark"] {
--background: oklch(0.13 0.01 264.53);
--foreground: oklch(0.93 0.01 264.53);
--primary: oklch(0.62 0.16 145);
--primary-foreground: oklch(0.13 0.01 264.53);
/* ... more variables */
}
Edit these files to customize colors, borders, shadows, and more.
TypeScript usage
Use the useTheme hook from remix-themes:
import { Theme, useTheme } from "remix-themes";
function MyComponent() {
const [theme, setTheme] = useTheme();
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(Theme.DARK)}>
Switch to Dark
</button>
<button onClick={() => setTheme(Theme.LIGHT)}>
Switch to Light
</button>
</div>
);
}
Server-side theme access
Access the current theme in loaders and actions:
import { LoaderFunctionArgs } from "@remix-run/node";
import { themeSessionResolver } from "@/lib/sessions.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { getTheme } = await themeSessionResolver(request);
const currentTheme = getTheme();
// Use theme for server-side logic
return { theme: currentTheme };
}
Production deployment
Before deploying to production:
Set session secret
Add a strong session secret to your environment:SESSION_SECRET=$(openssl rand -base64 32)
Configure domain
Update the cookie domain in lib/sessions.server.ts:...(isProduction
? { domain: "yourdomain.com", secure: true }
: {}),
Enable secure cookies
Ensure secure: true is set for production to require HTTPS.
Next steps
Browse Themes
Explore all 40+ available themes
Theme Picker
Preview themes in real-time
Next.js Setup
Install themes in Next.js
Astro Setup
Install themes in Astro