Remix
Complete guide to installing tweakcn/theme-picker themes in your Remix application. Uses remix-themes for server-side theme persistence.
Prerequisites
Keep :root and .dark as fallbacks
tweakcn/theme-picker themes use data-theme attributes. Keep your :root and .dark variable blocks as fallbacks to prevent flash of unstyled content before the theme loads.
remix-themes package for this.Installation
Install the theme system
Run this single command to install the complete theme system for Remix. This includes all 42+ themes, the theme CSS, and everything you need.
$ pnpm dlx shadcn@latest add https://tweakcn-picker.vercel.app/r/remix/theme-system.jsonThen install remix-themes for server-side theme persistence:
1npm install remix-themesUpdate your Tailwind CSS
Modify your tailwind.css to support both class and root selectors:
1.dark,2:root[class~="dark"] {3 --background: 222.2 84% 4.9%;4 --foreground: 210 40% 98%;5 /* ... other dark mode variables ... */6}Create session storage and theme resolver
Create a file to handle theme sessions:
1import { createCookieSessionStorage } from "@remix-run/node"2import { createThemeSessionResolver } from "remix-themes"34const isProduction = process.env.NODE_ENV === "production"56const sessionStorage = createCookieSessionStorage({7 cookie: {8 name: "theme",9 path: "/",10 httpOnly: true,11 sameSite: "lax",12 secrets: ["s3cr3t"], // Replace with your own secret13 ...(isProduction14 ? { domain: "your-production-domain.com", secure: true }15 : {}),16 },17})1819export const themeSessionResolver = createThemeSessionResolver(sessionStorage)Set up the root layout
Update your root.tsx to include the ThemeProvider:
1import clsx from "clsx"2import {3 PreventFlashOnWrongTheme,4 ThemeProvider,5 useTheme,6} from "remix-themes"7import type { LoaderFunctionArgs } from "@remix-run/node"8import {9 Links,10 LiveReload,11 Meta,12 Outlet,13 Scripts,14 ScrollRestoration,15 useLoaderData,16} from "@remix-run/react"1718import { themeSessionResolver } from "./sessions.server"1920export async function loader({ request }: LoaderFunctionArgs) {21 const { getTheme } = await themeSessionResolver(request)22 return {23 theme: getTheme(),24 }25}2627export default function AppWithProviders() {28 const data = useLoaderData<typeof loader>()29 return (30 <ThemeProvider31 specifiedTheme={data.theme}32 themeAction="/action/set-theme"33 >34 <App />35 </ThemeProvider>36 )37}3839function App() {40 const data = useLoaderData<typeof loader>()41 const [theme] = useTheme()4243 return (44 <html lang="en" className={clsx(theme)}>45 <head>46 <meta charSet="utf-8" />47 <meta name="viewport" content="width=device-width, initial-scale=1" />48 <Meta />49 <PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />50 <Links />51 </head>52 <body>53 <Outlet />54 <ScrollRestoration />55 <Scripts />56 <LiveReload />57 </body>58 </html>59 )60}Create the theme action route
Create an action route to handle theme changes:
1import { createThemeAction } from "remix-themes"2import { themeSessionResolver } from "../sessions.server"34export const action = createThemeAction(themeSessionResolver)Use the included ThemeSwitcher
The CLI installs a complete ThemeSwitcher component. Import and use it anywhere in your app:
1import { ThemeSwitcher } from "@/components/theme-switcher"23export function Header() {4 return (5 <header>6 <nav>{/* ... */}</nav>7 <ThemeSwitcher />8 </header>9 )10}The ThemeSwitcher includes a dropdown menu with all available themes, light/dark mode toggle, and displays the current theme with a color indicator.
Install Individual Themes
$ pnpm dlx shadcn@latest add https://tweakcn-picker.vercel.app/r/theme-catppuccin.jsonReplace theme-catppuccin with any theme: theme-claude, theme-vercel, etc.
remix-themes as shown above.