Provider
The single app-root wrapper — theme, toasts, tooltip coordination, and text direction. Mount it once; every VegaStack component below it just works.
- Status
- Since
0.2.0- Accessibility pattern
- no rendered UI
Last updated
Install
Add Provider from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/providerThe same command installs the registry items it composes: @vegastack/toast.
It also adds the sanctioned engine to your package.json: next-themes (theme runtime engine).
Usage
Wrap your app root, exactly once — in Next.js that's app/layout.tsx. The host
<html> element needs suppressHydrationWarning because next-themes mutates it on
the client before hydration:
// 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 — and what silently breaks without it
| Wiring | Without the provider |
|---|---|
Dark mode (next-themes, attribute="class", system-aware) | Theme toggles do nothing; the .dark class never reaches <html> |
Toasts (ToastProvider + the Toaster portal) | Every toast(...) call — from CopyButton, forms, anywhere — silently does nothing. No error, no toast. |
Tooltip coordination (Base UI Tooltip.Provider) | Tooltips lose shared-delay: hover across a toolbar and each tooltip re-waits the full open delay |
Text direction (Base UI DirectionProvider) | RTL layouts break for direction-aware components |
Examples
Theme toggle
useVegaStackTheme() (exported from the same file) is a thin wrapper over
next-themes' useTheme() — use it anywhere below the provider:
"use client";
import { useVegaStackTheme } from "@/components/ui/provider";
import { Button } from "@/components/ui/button";
export function ThemeToggle() {
const { resolvedTheme, setTheme } = useVegaStackTheme();
return (
<Button
variant="ghost"
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
>
Toggle theme
</Button>
);
}Tooltip coordination
Hover the first button, then move across — with the provider mounted, the rest open instantly instead of each re-waiting the open delay:
The mount-once rule
The bundled <Toaster /> is a portal that must exist exactly once. Never nest a
second VegaStackProvider, and if some other part of your app already renders a
<Toaster />, suppress the bundled one:
<VegaStackProvider toaster={false}>{children}</VegaStackProvider>You can also hand it a customized element instead:
<VegaStackProvider toaster={<Toaster position="top-center" />}>
{children}
</VegaStackProvider>All remaining props pass through to next-themes — e.g. force a default:
<VegaStackProvider defaultTheme="dark" enableSystem={false}>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | — | The application subtree that receives theme, direction, tooltip, and toast context. |
direction | "ltr" | "rtl" | 'ltr' | Text direction for Base UI components. |
toaster | React.ReactNode | true | Controls the bundled Toaster. Mount-once portal toasters must not be
double-mounted, so pass false if you already render a <Toaster />
elsewhere, or pass your own element to override the default. |
Accessibility
The provider renders no visible UI of its own — context providers plus the Toaster
portal. Toast announcements come from the toast viewport's labelled live region (see
Toast); disableTransitionOnChange prevents a
full-page transition flash on theme switch, complementing the global
prefers-reduced-motion reset in the base layer.
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test |
| Visual | default |