Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Provider setup

The one mount-once wrapper your app needs — what it wires, where it goes, and every way it can go wrong.

Last updated

VegaStackProvider is the app-root wrapper that supplies the invisible plumbing many components depend on. Install and mount it before adding any other component — several failure modes below are silent.

pnpm dlx shadcn@latest add @vegastack/provider
app/layout.tsx
import { VegaStackProvider } from "@/components/ui/provider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <VegaStackProvider>{children}</VegaStackProvider>
      </body>
    </html>
  );
}

What it wires

WiringWithout the provider you get
Dark mode — next-themes, attribute="class", system-aware, no-flashtoggles that do nothing; .dark never reaches <html>
ToastsToastProvider plus the Toaster portaltoast(...) calls that silently do nothing — no error anywhere
Tooltip coordination — Base UI Tooltip.Providerevery tooltip re-waits its full open delay; toolbars feel broken
Text direction — Base UI DirectionProviderRTL layouts break

Full component reference: Provider.

The three rules

  1. Exactly once, at the root. Never nest a second provider — the bundled Toaster is a mount-once portal and double-mounting double-renders every toast.
  2. suppressHydrationWarning on <html>. next-themes sets the theme class on the client before hydration; without the attribute React logs hydration mismatch warnings on every load.
  3. If something else already mounts a <Toaster />, suppress the bundled one: <VegaStackProvider toaster={false}> — or replace it: toaster={<Toaster position="top-center" />}.

Theme toggle (copy-paste)

components/theme-toggle.tsx
"use client";
import { Moon, Sun } from "lucide-react";
import { useVegaStackTheme } from "@/components/ui/provider";
import { Button } from "@/components/ui/button";

export function ThemeToggle() {
  const { resolvedTheme, setTheme } = useVegaStackTheme();
  const isDark = resolvedTheme === "dark";
  return (
    <Button
      variant="ghost"
      size="md"
      aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
      onClick={() => setTheme(isDark ? "light" : "dark")}
    >
      {isDark ? <Sun /> : <Moon />}
    </Button>
  );
}

resolvedTheme is undefined until mounted (SSR can't know the system theme) — the component above is hydration-safe as written; don't branch on it during render for anything layout-affecting.

Theme choice persists in localStorage automatically and follows the OS setting until the user picks explicitly (defaultTheme="system").

Non-Next.js apps

The provider is plain React — in Vite/anything else, wrap your root render instead of a layout file. The suppressHydrationWarning note is Next-specific (it exists because of SSR); pure-CSR apps don't need it.

Symptom → cause table

SymptomCause
toast('…') does nothing, zero errorsno provider (or it's below the component calling toast)
Toasts appear twicetwo Toasters — a nested provider or an extra manual <Toaster />; use toaster={false}
Hydration-mismatch warning mentioning <html> classmissing suppressHydrationWarning
Dark mode flashes wrong theme on loadprovider not at the root layout, or a competing theme script
Tooltips each wait ~600ms even in a toolbarno provider (no shared Tooltip.Provider)

On this page