BETTER-AUTH. UI
Plugins

Theme

Add theme selection with system, light, and dark modes to your Solid/Zaidan authentication flow.

The theme plugin adds theme selection to your authentication UI. Users can switch between system, light, and dark themes from the user button dropdown and account settings.

Setup

Install the UI plugin

Run the shadcn CLI to install the theme plugin factory, preference helpers, and copied Theme UI components:

npx shadcn@latest add https://better-auth-ui.com/r/solid/theme.json

Register the UI plugin

The plugin is theme-library agnostic. The recommended Zaidan setup is to use the copied theme helper from src/lib/theme.ts: run themeScript before Solid hydrates, sync the document preference in your provider shell, then register themePlugin() so the plugin's slot components read the persisted theme state.

src/routes/__root.tsx
import { themeScript } from "@/lib/theme"

export const Route = createRootRoute({
  head: () => ({
    scripts: [{ children: themeScript }] 
  })
})
src/components/providers.tsx
import { onCleanup, onMount } from "solid-js"
import { AuthProvider } from "@/components/auth/auth-provider"
import { themePlugin } from "@/lib/auth/theme-plugin"
import { authClient } from "@/lib/auth-client"
import { syncDocumentThemePreference } from "@/lib/theme"

export function Providers(props: { children?: JSX.Element }) {
  onMount(() => {
    const cleanup = syncDocumentThemePreference() 

    onCleanup(cleanup)
  })

  return (
    <AuthProvider
      authClient={authClient}
      plugins={[
        themePlugin() 
      ]}
    >
      {props.children}
    </AuthProvider>
  )
}

When the plugin is registered, <UserButton /> renders the Theme menu item through userMenuItems, and account settings render <Appearance /> through accountCards.

Or pass static theme state

If you manage the theme with a custom Solid signal or controller, pass both theme and setTheme from that stateful source. The plugin receives the current value from the parent and the slot components call your setter when users change themes.

src/components/providers.tsx
import { createSignal } from "solid-js"
import type { ThemeMode } from "@/lib/theme"

const [theme, setTheme] = createSignal<ThemeMode>("system") 

<AuthProvider
  authClient={authClient}
  plugins={[
    themePlugin({ 
      theme: theme(), 
      setTheme: (nextTheme) => setTheme(nextTheme as ThemeMode), 
      themes: ["system", "light", "dark"] 
    }) 
  ]}
>
  {props.children}
</AuthProvider>

Use either the copied helper defaults or an explicit theme/setTheme pair when you need a custom controller.

Components

These previews use local theme state only. They do not call Better Auth endpoints or require a live session.

<UserButton />

The Theme menu item is automatically rendered in <UserButton /> when themePlugin() is registered.

Usage

import { AuthProvider } from "@/components/auth/auth-provider"
import { UserButton } from "@/components/auth/user/user-button"
import { themePlugin } from "@/lib/auth/theme-plugin"
import { authClient } from "@/lib/auth-client"

export function ThemeUserButtonDemo() {
  return (
    <AuthProvider authClient={authClient} plugins={[themePlugin()]}>
      {() => <UserButton />}
    </AuthProvider>
  )
}

<Appearance />

The <Appearance /> card is automatically rendered in account settings when themePlugin() is registered and the account page renders plugin-contributed accountCards.

Usage

import { Appearance } from "@/components/auth/theme/appearance"

export function AppearanceDemo() {
  return <Appearance />
}

Props

Prop

Type

Options

Prop

Type

Localization

Prop

Type

The copied Zaidan Theme components resolve labels from themePlugin({ localization }) and fall back to the default Theme localization.

Last updated on

On this page