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
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Provider before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify providerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/providerRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/toast, @vegastack/tooltip.
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.add(...) call — from a form, a save handler, anywhere — silently does nothing. No error, no toast. |
Tooltip coordination (TooltipProvider, the registry item) | 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 choice
useVegaStackTheme() (exported from the same file) is a thin wrapper over
next-themes' useTheme() — use it anywhere below the provider. Theme is a user preference, so it
belongs in the user menu as a radio group bound to theme, not in a local toggle:
"use client";
import { useVegaStackTheme } from "@/components/ui/provider";
import {
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from "@/components/ui/dropdown-menu";
export function ThemeRadioItems() {
const { theme, setTheme } = useVegaStackTheme();
return (
<DropdownMenuRadioGroup
value={theme ?? "system"}
onValueChange={(value) => setTheme(value)}
>
<DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
);
}The whole user menu, with these items in a Theme submenu, is in Provider setup.
Tooltip coordination
The provider mounts TooltipProvider — the same part you would install
with Tooltip — and sets its open and close delays from one place, so every tooltip in the app
opens on the same rhythm. 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. The provider's
ToastProvider carries the module toast manager, so toast() and useToastManager() feed the
same queue, and a <Toaster /> rendered below the provider reuses it rather than starting a second
store. 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 |