# Capability Gate

Capability Gate [#capability-gate]

The **Capability Gate** renders its children only when the device meets a stated requirement (e.g. `requires="webgpu"`), and otherwise renders a `fallback` slot with guidance. Local models have hard device requirements — LiteRT Gemma is WebGPU-only — so gating prevents a broken experience and gives a clear, themeable explanation instead. The capability check uses `useCapabilities`.

Preview [#preview]

```tsx
'use client';

import { CapabilityGate } from '@/components/capability-gate';

/**
 * Demo for CapabilityGate. Gates a "fast model" panel behind real WebGPU
 * support detected via useCapabilities — children render on a WebGPU device,
 * the fallback notice renders otherwise — plus a media-input example: the
 * webcam surface renders only when a camera is AVAILABLE (secure context +
 * `enumerateDevices()` reports a videoinput; detection never prompts —
 * permission is requested later, by the surface's own start action).
 */
export default function CapabilityGateDemo() {
  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <CapabilityGate requires="webgpu">
        <div className="rounded-lg border border-emerald-500/40 bg-emerald-500/5 px-4 py-3 text-sm">
          <span className="font-medium text-emerald-600 dark:text-emerald-400">
            WebGPU available
          </span>
          <p className="text-muted-foreground">
            The fast WebGPU model can run on this device.
          </p>
        </div>
      </CapabilityGate>

      <CapabilityGate requires="camera">
        <div className="rounded-lg border border-sky-500/40 bg-sky-500/5 px-4 py-3 text-sm">
          <span className="font-medium text-sky-600 dark:text-sky-400">Camera detected</span>
          <p className="text-muted-foreground">
            A webcam surface (e.g. live hand tracking) can be offered here. No permission prompt
            fired - the gate only checks availability.
          </p>
        </div>
      </CapabilityGate>

      <CapabilityGate requires="indexeddb">
        <p className="text-sm text-muted-foreground">
          IndexedDB is available - models and vectors can persist on-device.
        </p>
      </CapabilityGate>
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/local-first/capability-gate
```

Data source & dependencies [#data-source--dependencies]

**Data source:** detects device capabilities via the bundled `useCapabilities` navigator hook — self-contained, works in any React app, no backend required. (It mirrors the detection `useCapabilities` from `@localmode/react` performs, plus copy-owned `camera`/`microphone` media-input availability entries via `enumerateDevices()` — never prompting.)

* `@localmode/ui/lib/use-environment` — the bundled `useCapabilities` navigator hook (installed automatically as a registry dependency)
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `capability-gate.tsx` — the `CapabilityGate` component
* `lib/use-environment.ts` — the bundled `useCapabilities` navigator hook
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**CapabilityGate**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `requires` | `GateCapability` | — | **Required.** The device capability the children require. |
| `children` | `ReactNode` | — | **Required.** Rendered only when the capability is supported. |
| `fallback` | `ReactNode` | — | Rendered when the capability is unsupported. Defaults to a themed notice explaining the requirement. |
| `pending` | `ReactNode` | — | Rendered while detection is in flight. Defaults to a muted placeholder. |

Examples [#examples]

Gate a WebGPU-only model [#gate-a-webgpu-only-model]

```tsx
<CapabilityGate
  requires="webgpu"
  fallback={<p>This model needs WebGPU. Try Chrome 113+.</p>}
>
  <FastWebGPUModel />
</CapabilityGate>
```

Gate a media surface (camera / microphone) [#gate-a-media-surface-camera--microphone]

The `camera` and `microphone` capabilities gate on media-input **availability** — a secure context, `navigator.mediaDevices.getUserMedia` present, and `enumerateDevices()` reporting at least one device of that kind. The detection never prompts the user. Runtime **permission denial** is deliberately not gated: request permission from your own start action and surface a `getUserMedia` rejection as a recoverable error with retry.

```tsx
<CapabilityGate
  requires="camera"
  fallback={<p>No camera detected on this device.</p>}
>
  <WebcamHandTracking /> {/* calls getUserMedia on its own Start action */}
</CapabilityGate>

<CapabilityGate requires="microphone">
  <RecordAndClassify />
</CapabilityGate>
```

Customization [#customization]

The default fallback and pending slots are themed amber/muted notices — pass your own `fallback`/`pending` nodes for full control. The `requires` union covers `webgpu`, `wasm`, `webnn`, `simd`, `threads`, `indexeddb`, `webworkers`, `sharedarraybuffer`, and the media-availability entries `camera` and `microphone`; add more by extending the `LABELS` map. Styled entirely with shadcn/ui CSS-variable utilities so it inherits your theme — because you own the copied file, every class and threshold is yours to change.