---
title: Gadgets — deterministic islands
description: Why third-party capability enters generated UI only through human-authored, operator-gated wrappers — and what that buys you in safety, caching, and LLM reliability.
---

A generated interface is LLM-written React. That works for layout, forms, and state — but the moment a UI needs a real map, a checkout flow, or the camera, "generate it" stops being an option. Nobody wants an LLM improvising a payments integration, and no model reliably reinvents Leaflet's lifecycle quirks on demand.

Gadgets are how ggui resolves that tension: **the LLM composes third-party capability but never authors it.** A gadget is a human-written, versioned, integrity-pinned wrapper around a browser library, exposed to generated code as a React hook with one uniform contract. The generated component calls `useLeafletMap(...)` the same way it calls `useState` — everything inside that hook is deterministic, reviewed code.

## One pattern, learned once

Every third-party library enters the system through the same factory ([`createGguiGadget`](/sdk/gadgets/)), so every gadget presents the same hook shape: call it with options, get back `{ value, status, start, … }`. The generator never sees raw library APIs — not Leaflet's imperative `L.map()` lifecycle, not Stripe's redirect dance. It sees one hook convention, which means:

- **Cold generations get it right more often.** The model pattern-matches against one shape it has seen many times, not N library idioms it has seen unevenly.
- **A new gadget costs the model nothing to learn.** The wrapper's teaching text — `description` (what), `usage` (when), `example` (a concrete call) — travels with the descriptor, and the generator reads it at synthesis time. Docs for the LLM are part of the spec, not an afterthought: a wrapper without them is rejected at module load.

## Two symmetric capability surfaces

Gadgets are the renderer-side half of a symmetric design ([glossary](/glossary/#capabilities)):

|         | Renderer                     | Agent                     |
| ------- | ---------------------------- | ------------------------- |
| Unit    | **gadget**                   | **tool**                  |
| Catalog | `clientCapabilities.gadgets` | `agentCapabilities.tools` |
| Gives   | something to render with     | something to act with     |

Both catalogs are declared per app and bounded by the **operator**, not the agent. An agent cannot smuggle a capability into a render by asking for it: a contract may only reference gadgets the operator registered on the app, and a render naming an unregistered one is rejected with `gadget_not_registered` before any code runs.

## The trust chain

Generated code runs in a sandboxed iframe with a closed Content-Security-Policy. Each gadget widens that sandbox **declaratively, not implicitly**:

- **Provenance** — the wrapper bundle is fetched by exact package + version, and marketplace-published bundles carry a `sha384` subresource-integrity pin stamped at publish time. What loads is byte-identical to what was published.
- **Network** — a gadget declares the origins it talks to (`connect: ["https://tile.openstreetmap.org"]`); the renderer unions exactly those into the iframe's CSP. A map gadget can fetch tiles; it cannot phone anywhere else, and neither can the generated code around it.
- **Secrets** — wrappers that need a token (Mapbox, Stripe) declare the `GGUI_PUBLIC_APP_*` keys they consume, and the operator supplies values through the app's public-env channel. Keys never appear in generated code.

The result: the only non-generated code in a render is code an operator explicitly registered, at a pinned version, with a declared network footprint.

## Why versioning feeds the cache

A gadget's `version` is part of the [blueprint cache key](/concepts/blueprints-and-the-cache/). Bump a wrapper and every cached generation built against the old version stops matching automatically — no stale UI silently calling a changed hook contract. This is the same discipline that makes blueprint reuse safe in general: the cache key captures everything the compiled component depends on.

## The standard library

Browser capabilities that need no third-party code ship as **STDLIB hooks** — `useGeolocation`, `useCamera`, `useClipboardWrite`, `useClipboardPaste`, `useNotifications`, `useFilePicker`, `useMicrophone`. They follow the same hook contract but require no operator registration; the generator can reach for them whenever the ask calls for one.

## What a gadget is not

- **Not a template.** A gadget renders one capability (a map, a chart, a checkout); the generated component decides where it sits, what feeds it, and what happens around it. Composition stays generative; capability stays deterministic.
- **Not agent-side.** Gadgets never execute in the agent's process and never see the conversation. Anything the agent should _do_ is a tool.
- **Not a behavior escape hatch.** The contract describes data flowing between agent and UI; gadgets extend what the _component code_ can do. Neither is a channel for smuggling the other — see [Sessions & the event model](/concepts/sessions-and-the-event-model/) for the placement rule.

## Next

- **[Gadgets SDK](/sdk/gadgets/)** — author a wrapper: the `createGguiGadget` factory, teaching-text budgets, bundle + types publishing, operator registration.
- **[Marketplace](/hosted/marketplace/)** — publish and install gadgets across apps.
- **[Blueprints and the cache](/concepts/blueprints-and-the-cache/)** — how versioned capability feeds exact-match reuse.