shadcn-ui/ui · error · Error

useTheme must be used within a ThemeProvider

Error message

useTheme must be used within a ThemeProvider

What it means

This error is thrown by the useTheme hook when React.useContext(ThemeProviderContext) returns undefined. The context is created with a default value of undefined (line 22-24), so any component consuming useTheme that is NOT rendered as a descendant of <ThemeProvider> reads undefined and the guard at line 225 throws. It is the standard 'hook used outside its provider' fail-fast pattern that prevents silently operating on missing theme state.

Source

Thrown at templates/vite-monorepo/apps/web/src/components/theme-provider.tsx:226

    () => ({
      theme,
      setTheme,
    }),
    [theme, setTheme]
  )

  return (
    <ThemeProviderContext.Provider {...props} value={value}>
      {children}
    </ThemeProviderContext.Provider>
  )
}

export const useTheme = () => {
  const context = React.useContext(ThemeProviderContext)

  if (context === undefined) {
    throw new Error("useTheme must be used within a ThemeProvider")
  }

  return context
}

View on GitHub (pinned to efac598707)

Solutions

  1. Ensure the component calling useTheme is a descendant of <ThemeProvider> in the React tree. Move it inside the provider in your entry file, e.g. wrap <App/> with <ThemeProvider defaultTheme="system" storageKey="theme"> so every consumer sits beneath it.
  2. If the error surfaces in tests, wrap the rendered component: render(<ThemeProvider><MyComponent/></ThemeProvider>) in Vitest/RTL, or add a global decorator in Storybook (parameters.decorators) that supplies ThemeProvider.
  3. If a portal or separate React root needs the theme, either render it within the existing provider tree, or mount an additional <ThemeProvider> around that secondary root.
  4. Provide a safe fallback by guarding before calling useTheme: check React.useContext(ThemeProviderContext) directly and supply a default instead of throwing, if a missing provider is an expected state.
  5. Remove the useTheme call from components that do not strictly need live theme state, or pass theme/setTheme down as explicit props from a component that is already inside the provider.

Example fix

// before (main.tsx) — App renders above/without ThemeProvider
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

// after — ThemeProvider wraps the entire consuming tree
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ThemeProvider defaultTheme="system" storageKey="theme">
      <App />
    </ThemeProvider>
  </React.StrictMode>
)
Defensive patterns

Strategy: validation

Validate before calling

import * as React from 'react'
import { ThemeProviderContext } from './theme-provider'

function useThemeOptional(defaultValue = { theme: 'system' as const, setTheme: () => {} }) {
  const ctx = React.useContext(ThemeProviderContext)
  if (ctx === undefined) {
    // Caller is outside ThemeProvider; avoid throwing by returning a default.
    return defaultValue
  }
  return ctx
}

Type guard

import * as React from 'react'
import { ThemeProviderContext, type ThemeProviderState } from './theme-provider'

function hasThemeProvider(ctx: unknown): ctx is ThemeProviderState {
  return (
    typeof ctx === 'object' &&
    ctx !== null &&
    'theme' in ctx &&
    typeof (ctx as ThemeProviderState).setTheme === 'function'
  )
}

// usage:
//   const ctx = React.useContext(ThemeProviderContext)
//   if (!hasThemeProvider(ctx)) return null // or render a fallback

Try / catch

// React render throws are not catchable by try/catch around useTheme.
// Guard at the component boundary instead:
function MyComponent() {
  const ctx = React.useContext(ThemeProviderContext)
  if (ctx === undefined) {
    // Render a non-theme-dependent fallback rather than calling useTheme
    return <div>Theme unavailable</div>
  }
  // ctx is now narrowed to ThemeProviderState
  return <div>Current theme: {ctx.theme}</div>
}

Prevention

When it happens

Trigger: Calling useTheme() in a component that is rendered outside the <ThemeProvider> subtree. Concrete cases in this template: (1) a component placed in main.tsx or App.tsx ABOVE or as a sibling to <ThemeProvider>, (2) a component rendered through a portal whose React tree parent is not ThemeProvider, (3) using useTheme in a Storybook story or unit test (Vitest/RTL) without wrapping the component in ThemeProvider, (4) a second root created with ReactDOM.createRoot that is not wrapped, (5) an error boundary's fallback component that calls useTheme but is mounted outside the provider.

Common situations: Adding a new component that needs the current theme but forgetting to place it inside the provider tree; restructuring the app entry (main.tsx) and accidentally moving ThemeProvider below the component; testing components in isolation without the provider decorator; SSR or pre-render paths where a theme-consuming component renders before ThemeProvider mounts; refactoring that splits the tree into multiple roots; upgrading a shadcn-style theme setup and losing the provider wrapper.

Related errors


AI-assisted analysis of shadcn-ui/ui@efac598707 (2026-08-12). Data as JSON: /api/errors/738dcb7ad5b5b192. Report an issue: GitHub.