> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding dark mode back

> Vibestrap ships light-only. Here's the five-step recipe to re-enable a light/dark toggle.

Vibestrap intentionally ships **light-mode only**. The reason: every
component change becomes a two-mode tax, the marketing site reads
better as a single confident theme, and most of the premium scaffolds
we benchmark against (Stripe, Loops, Resend, Clerk, tuwa.ai) don't
ship dark mode either.

That said, dark mode is a real preference for many developers. If your
end-product needs it, here's the five-step recipe to put it back.

## Step 1 — Reinstall `next-themes`

```bash theme={null}
pnpm add next-themes
```

## Step 2 — Wrap with `ThemeProvider`

Edit `src/components/providers.tsx`:

```tsx theme={null}
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from 'next-themes';
import { useState } from 'react';
import { Toaster } from 'sonner';

export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: { staleTime: 60 * 1000, refetchOnWindowFocus: false },
        },
      })
  );
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
      <QueryClientProvider client={queryClient}>
        {children}
        <Toaster position="top-center" richColors />
      </QueryClientProvider>
    </ThemeProvider>
  );
}
```

Note: drop the `theme="light"` prop from `<Toaster>` — sonner will
follow the system theme automatically through `next-themes`.

## Step 3 — Re-add `.dark` block in `globals.css`

Add the variant directive at the top of `src/app/globals.css` (right
after the `@plugin` line):

```css theme={null}
@custom-variant dark (&:is(.dark *));
```

And add a `.dark { ... }` block right after `:root { ... }` defining the
inverse palette. For the editorial cream-mono baseline, that means
something like:

```css theme={null}
.dark {
  --background: oklch(0.16 0.01 85);
  --foreground: oklch(0.96 0.012 85);
  --card: oklch(0.20 0.012 85);
  --card-foreground: oklch(0.96 0.012 85);
  --popover: oklch(0.20 0.012 85);
  --popover-foreground: oklch(0.96 0.012 85);
  --primary: oklch(0.96 0.012 85);
  --primary-foreground: oklch(0.16 0.01 85);
  --secondary: oklch(0.26 0.012 85);
  --secondary-foreground: oklch(0.96 0.012 85);
  --muted: oklch(0.26 0.012 85);
  --muted-foreground: oklch(0.7 0.005 80);
  --accent: oklch(0.26 0.012 85);
  --accent-foreground: oklch(0.96 0.012 85);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 14%);
  --ring: oklch(0.55 0.2 250);
}
```

Tune values to your skin — these mirror the cream-mono editorial
defaults.

## Step 4 — Restore the toggle component

Recreate `src/components/layout/theme-toggle.tsx`:

```tsx theme={null}
'use client';

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';

export function ThemeToggle() {
  const { setTheme, resolvedTheme } = useTheme();
  const next = resolvedTheme === 'dark' ? 'light' : 'dark';
  return (
    <Button
      variant="ghost"
      size="icon"
      aria-label="Toggle theme"
      onClick={() => setTheme(next)}
    >
      <Sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  );
}
```

Then mount it in `src/components/layout/header.tsx`:

```tsx theme={null}
import { ThemeToggle } from './theme-toggle';
// …
<div className="flex items-center gap-1">
  <LocaleSwitcher />
  <ThemeToggle />
  <UserNav />
</div>
```

## Step 5 — Restore `dark:` Tailwind variants where they matter

The places in Vibestrap that used dark-variant compensations were:

* Markdown prose pages — add `dark:prose-invert` back to the
  `<div className="prose ...">` wrappers under
  `src/app/[locale]/(marketing)/{terms,privacy,refund,license,about,changelog,blog/[slug]}/page.tsx`.
* Status colors in `src/components/ai/*` — `text-emerald-600` etc.
  benefit from `dark:text-emerald-400` for readable contrast on
  dark.
* `src/components/ui/alert.tsx` — destructive variant readability.
* `src/components/turnstile-field.tsx` — pass `resolvedTheme` to the
  Turnstile widget so its embedded UI matches.

Search for the `dark:*` usages you need with:

```bash theme={null}
grep -rn "text-emerald-600\|text-amber-600\|prose " src/
```

…and add a paired `dark:*` class alongside.

## Verification

After the five steps, run the gates:

```bash theme={null}
pnpm typecheck && pnpm lint && pnpm check:i18n && pnpm build
```

Toggle the theme in the browser and confirm:

* Marketing pages render cleanly in both themes.
* Status / accent colors are readable in dark.
* Sonner toasts pick up the active theme.
* Turnstile widget colors match.

## Why we don't ship it ourselves

Inherited maintenance: every component change becomes "does this work
in dark too?". Most buyers ship to mainstream consumer audiences who
don't toggle dark mode on marketing sites. Editorial / cream-mono
aesthetics — Vibestrap's default skin — are light-first by design.

If you go through the recipe above and discover gaps in our
documentation, please send us a note — we'll improve this page rather
than re-add dark mode to the default branch.
