<!-- Protocore Design System — PdsProvider -->
# PdsProvider

- **Category:** Utilities (`utilities`)
- **Slug:** `utilities/pds-provider`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { PdsProvider, usePdsContext } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/utilities/pds-provider

> Stamps theme/accent/density/env on a subtree and exposes them via context.

## When to use it

**PdsProvider** does two jobs: it **stamps the theming attributes** (`data-theme`, `data-accent`, `data-density`, `data-env`) on its subtree, and it exposes them — plus `setTheme`/`setAccent` — through **PdsContext** for runtime toggles and density-aware components. Wrap your app once near the root. Apps typically *also* set `data-theme` on `<html>` (for the initial paint and full-page background) and use this provider for the reactive context. Theme and accent are controllable (`theme`/`defaultTheme` + `onThemeChange`, same for accent) following the standard PDS controlled/uncontrolled pattern; density and env are plain props. Because attributes are scoped to the subtree, you can nest providers to preview a different theme or env in one panel — as the density/env demo does.

## usePdsContext()

Call `usePdsContext()` inside any descendant to read `{ theme, setTheme, accent, setAccent, density, env }`. It **throws** if used outside a `<PdsProvider>`, so a caught error means a component is mounted without the provider above it. Use the setters to build theme/accent toggles (they respect controlled mode — if you passed `theme`, drive it through `onThemeChange` instead).

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `accent` | `enum` | no | — | Controlled accent. Pair with `onAccentChange`. |
| `asChild` | `boolean` | no | `false` | Merge props onto the child element instead of rendering a wrapper div. |
| `className` | `string` | no | — | — |
| `defaultAccent` | `enum` | no | `green` | Initial accent in uncontrolled mode. |
| `defaultTheme` | `enum` | no | `dark` | Initial theme in uncontrolled mode. |
| `density` | `enum` | no | `default` | UI density; stamps `data-density`. |
| `env` | `enum` | no | — | Deployment environment; stamps `data-env` for env chrome. |
| `onAccentChange` | `((accent: "green" \| "blue" \| "red" \| "amber" \| "violet") => void)` | no | — | Fires when the accent changes. |
| `onThemeChange` | `((theme: "dark" \| "light") => void)` | no | — | Fires when the theme changes. |
| `style` | `CSSProperties` | no | — | — |
| `theme` | `enum` | no | — | Controlled theme. Pair with `onThemeChange`. |

## Examples

### Live theme switching

PdsProvider stamps `data-theme` on its wrapper, and every token resolves against that attribute — so flipping the theme recolors the whole subtree live, scoped to this box, no reload. The child reads and sets the theme through `usePdsContext()`.

```tsx
import {
  PdsProvider,
  usePdsContext,
  SegmentedControl,
  Card,
  Button,
  Badge,
  StatusDot,
  Stack,
  HStack,
  Text,
} from "@protocore/pds";

function Preview() {
  const { theme, setTheme } = usePdsContext();
  return (
    <Stack gap={4}>
      <SegmentedControl
        aria-label="Theme"
        value={theme}
        onValueChange={(v) => setTheme(v as "dark" | "light")}
        items={[
          { value: "dark", label: "Dark" },
          { value: "light", label: "Light" },
        ]}
      />
      <Card title="ledger-node-01" subtitle="eu-central-1">
        <Stack gap={3}>
          <HStack gap={2} align="center">
            <StatusDot tone="success" pulse aria-label="Healthy" />
            <Text size="sm" color="muted">
              Syncing block 21,904,118
            </Text>
          </HStack>
          <HStack gap={2} align="center">
            <Badge tone="success">Live</Badge>
            <Badge tone="neutral">v4.2.0</Badge>
          </HStack>
          <Button variant="primary">Open console</Button>
        </Stack>
      </Card>
    </Stack>
  );
}

export default function Demo() {
  // The provider stamps data-theme on its subtree, so every pds component
  // inside recolors live — no page reload, scoped to this box.
  return (
    <PdsProvider
      defaultTheme="dark"
      style={{
        padding: "var(--pds-space-5)",
        background: "var(--pds-color-canvas)",
        border: "1px solid var(--pds-border-faint)",
      }}
    >
      <Preview />
    </PdsProvider>
  );
}
```

### Accent

`accent` stamps `data-accent`, remapping `--pds-accent` — the single brand signal used by focus rings, the one live number, and selected markers. Five accents ship: green (default), blue, red, amber, violet.

```tsx
import {
  PdsProvider,
  usePdsContext,
  SegmentedControl,
  Stack,
  HStack,
  Button,
  StatusDot,
  Text,
} from "@protocore/pds";

const ACCENTS = ["green", "blue", "red", "amber", "violet"] as const;

function Preview() {
  const { accent, setAccent } = usePdsContext();
  return (
    <Stack gap={4}>
      <SegmentedControl
        aria-label="Accent"
        value={accent}
        onValueChange={(v) => setAccent(v as (typeof ACCENTS)[number])}
        items={ACCENTS.map((a) => ({ value: a, label: a }))}
      />
      {/* --pds-accent drives focus rings, the single live number, selected markers */}
      <HStack gap={3} align="center">
        <StatusDot tone="info" aria-label="Signal" />
        <Text mono style={{ color: "var(--pds-accent)" }}>
          1,284 tx/s
        </Text>
        <Button variant="secondary">Focus me</Button>
      </HStack>
    </Stack>
  );
}

export default function Demo() {
  return (
    <PdsProvider
      defaultAccent="green"
      style={{
        padding: "var(--pds-space-5)",
        background: "var(--pds-color-surface)",
        border: "1px solid var(--pds-border-faint)",
      }}
    >
      <Preview />
    </PdsProvider>
  );
}
```

## Do & don't

**Do**

- Wrap your app once near the root, and also set `data-theme` on `<html>` for the first paint and page background.
- Read and toggle theme/accent through `usePdsContext()` rather than manipulating attributes yourself.
- Nest a provider to scope a different theme or `env` to a preview panel.
- Use `defaultTheme`/`defaultAccent` for uncontrolled, or the controlled prop + `onChange` when app state owns the value.

**Don't**

- Don't call `usePdsContext()` outside a provider — it throws by design.
- Don't scatter many providers across a page for styling; one near the root plus deliberate nested previews.
- Don't set `data-theme` on `<html>` and forget the provider — density-aware components and toggles need the context.
- Don't mix controlled `theme` with `setTheme` writes; drive controlled state through `onThemeChange`.

## Accessibility

**Notes**

- PdsProvider renders a plain `<div>` (or merges onto its child with `asChild`) and adds no roles — it's a context/attribute host, not a landmark.
- Theming affects color only; it never changes DOM order or focus, so keyboard and screen-reader flow are unaffected.
- Respect the user's OS color-scheme preference when choosing the initial theme, and keep contrast within AA across whichever accent you expose.
- Density changes control heights, not hit targets' semantics — keep `compact` targets large enough to remain comfortably tappable.

## Related

`visually-hidden`, `env-badge`, `env-strip`, `segmented-control`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
