Remix
Framework Guide

Remix

Complete guide to installing tweakcn/theme-picker themes in your Remix application. Uses remix-themes for server-side theme persistence.

Prerequisites

Remix 2.0+
shadcn/ui initialized in your project
Tailwind CSS configured

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.

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.json

Then install remix-themes for server-side theme persistence:

Terminalbash
1npm install remix-themes

Update your Tailwind CSS

Modify your tailwind.css to support both class and root selectors:

app/tailwind.csscss
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:

app/sessions.server.tsxtsx
1import { createCookieSessionStorage } from "@remix-run/node"
2import { createThemeSessionResolver } from "remix-themes"
3
4const isProduction = process.env.NODE_ENV === "production"
5
6const sessionStorage = createCookieSessionStorage({
7 cookie: {
8 name: "theme",
9 path: "/",
10 httpOnly: true,
11 sameSite: "lax",
12 secrets: ["s3cr3t"], // Replace with your own secret
13 ...(isProduction
14 ? { domain: "your-production-domain.com", secure: true }
15 : {}),
16 },
17})
18
19export const themeSessionResolver = createThemeSessionResolver(sessionStorage)

Set up the root layout

Update your root.tsx to include the ThemeProvider:

app/root.tsxtsx
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"
17
18import { themeSessionResolver } from "./sessions.server"
19
20export async function loader({ request }: LoaderFunctionArgs) {
21 const { getTheme } = await themeSessionResolver(request)
22 return {
23 theme: getTheme(),
24 }
25}
26
27export default function AppWithProviders() {
28 const data = useLoaderData<typeof loader>()
29 return (
30 <ThemeProvider
31 specifiedTheme={data.theme}
32 themeAction="/action/set-theme"
33 >
34 <App />
35 </ThemeProvider>
36 )
37}
38
39function App() {
40 const data = useLoaderData<typeof loader>()
41 const [theme] = useTheme()
42
43 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:

app/routes/action.set-theme.tstsx
1import { createThemeAction } from "remix-themes"
2import { themeSessionResolver } from "../sessions.server"
3
4export const action = createThemeAction(themeSessionResolver)

Use the included ThemeSwitcher

The CLI installs a complete ThemeSwitcher component. Import and use it anywhere in your app:

Using the ThemeSwitchertsx
1import { ThemeSwitcher } from "@/components/theme-switcher"
2
3export 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.json

Replace theme-catppuccin with any theme: theme-claude, theme-vercel, etc.

Troubleshooting