Dynamic theme

A React provider for dynamic theming — user-selected colors, scoped palettes per section, light/dark mode switching, and image-based color extraction.

Install

1. Add the Theme component

npx shadcn@latest add https://material-shadcn.vercel.app/r/theme

Adds components/theme.tsx and installs material-shadcn automatically.

2. Wrap your app

import { Theme } from "@/components/theme"

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Theme seed="#6750A4">
          {children}
        </Theme>
      </body>
    </html>
  )
}

The root <Theme> applies CSS variables to <html>, manages light/dark mode, and persists user preferences to localStorage.

3. Control the theme

import { useTheme } from "@/components/theme"

function ThemePicker() {
  const { seed, setSeed, cycleColorMode } = useTheme()

  return (
    <div>
      <input
        type="color"
        value={seed}
        onChange={(e) => setSeed(e.target.value)}
      />
      <button onClick={cycleColorMode}>
        Toggle dark mode
      </button>
    </div>
  )
}

Scoped themes

Nest <Theme> inside a parent <Theme> to scope a different palette to any section. It renders a <div> with CSS variables as inline styles — all child components pick them up via cascade.

import { Theme } from "@/components/theme"
import { Variant } from "material-shadcn"

function ProductCard({ imageColor }: { imageColor: string }) {
  return (
    <Theme seed={imageColor} variant={Variant.CONTENT}>
      <div className="bg-card text-card-foreground p-4 rounded-lg">
        <h3 className="text-primary">Product Title</h3>
        <button className="bg-primary text-primary-foreground">
          Buy now
        </button>
      </div>
    </Theme>
  )
}

You can pass className, style, and any other div props to the nested <Theme>.

Image-based seeding

Pass an HTMLImageElement to setSeed() or as the seed prop to extract a dominant color and generate a theme from it.

function ImageTheme() {
  const { setSeed } = useTheme()

  return (
    <img
      src="/hero.jpg"
      onLoad={(e) => setSeed(e.currentTarget)}
      alt="Hero"
    />
  )
}

Color mode

The root <Theme> manages light/dark mode with three states: system, light, and dark.

function ColorModeToggle() {
  const { colorMode, setColorMode, cycleColorMode } = useTheme()

  return (
    <div>
      {/* Cycle through system → light → dark */}
      <button onClick={cycleColorMode}>
        {colorMode}
      </button>

      {/* Or set directly */}
      <button onClick={() => setColorMode('dark')}>
        Dark mode
      </button>
    </div>
  )
}

Persistence

By default, seed color, variant, and color mode are saved to localStorage under the key material-shadcn-theme. Pass storageKey={null} to disable persistence.

{/* Custom storage key */}
<Theme storageKey="my-app-theme">
  {children}
</Theme>

{/* Disable persistence */}
<Theme storageKey={null}>
  {children}
</Theme>

Next: See Reference for all props and hook return values.