Skip to content
Component installs need the registry setup
VegaStack Design

Provider

The single app-root wrapper — theme, toasts, tooltip coordination, and text direction. Mount it once; every VegaStack component below it just works.

Status
stable
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 provider

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/provider

Run 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

WiringWithout 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

PropTypeDefaultDescription
children*React.ReactNode—The application subtree that receives theme, direction, tooltip, and toast context.
direction"ltr" | "rtl"'ltr'Text direction for Base UI components.
toasterReact.ReactNodetrueControls 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.

ContractStates tested
Behaviourdefault
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault

Do / Don't

Do
Mount VegaStackProvider once at the app root, and offer theme as a Light / Dark / System radio group in the user menu, bound to useVegaStackTheme.
Don't
Nest providers or mount a second Toaster — toasts will double-render; pass toaster={false} instead.

On this page