# @onkernel/vault-react

> Unstyled React forms for collecting Kernel vault credentials

Latest version **0.2.0** (published 2026-09-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install @onkernel/vault-react
pnpm add @onkernel/vault-react
yarn add @onkernel/vault-react
bun add @onkernel/vault-react
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.2.0 |
| Published | 2026-09-23 |
| First published | 2026-09-19 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 98.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Kernel Technologies, Inc. |
| Maintainers | masnwilliams, roee_kernel, ulziibay, raf-kernel, cat-kernel |
| Keywords | kernel, credentials, vault, react |

## Links

- npm: https://www.npmjs.com/package/@onkernel/vault-react
- Repository: https://github.com/kernel/kernel
- Homepage: https://onkernel.com
- Issues: https://github.com/kernel/kernel/issues
- npm.io page: https://npm.io/package/@onkernel/vault-react

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 0.2.0 (latest) — 2026-09-23
- 0.1.0 — 2026-09-19

## README

# vault-react: react library for custom-branded KERNEL vault collection forms

Unstyled React credential collection, shared by the isolated collector and customer pages.

Install the package from npm:

```sh
npm install @onkernel/vault-react
```

The recommended customer flow uses a same-origin page and API endpoint:

```text
https://app.example.com/credentials/collect#token=<32-byte base64url capability>
https://app.example.com/api/credential-requests/current
```

The page reads the token from the URL fragment and sends it only in `Authorization: Bearer <token>` requests to the same-origin endpoint. Fragments are not sent in page requests or referrers. Do not put the token in a path or query string.

```tsx
import { CredentialForm, CredentialFormError, safeCredentialItem } from '@onkernel/vault-react';

<CredentialForm
  className="my-credentials"
  item={safeCredentialItem(itemFromYourBackend)}
  onSubmit={async ({ version, fields }) => {
    const response = await fetch('/api/credential-requests/current', {
      method: 'PATCH',
      credentials: 'omit',
      cache: 'no-store',
      headers: {
        Authorization: `Bearer ${tokenFromLocationHash}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ version, fields }),
    });
    if (response.status === 409) throw new CredentialFormError('stale');
    if (!response.ok) throw new CredentialFormError('invalid');
  }}
/>;
```

## form contract

- `item` contains `id`, `type`, `version`, `spec.fields`, and `state.fields`. The component snapshots that read model and version. A newer item/version prop blocks submission rather than silently rebasing edits. Remount explicitly after obtaining new data and confirming discarded edits.
- Every text, email, and password field is rendered in definition order. An optional human-readable `label` is displayed when present, with stable `name` as the fallback and the control/submission key. TOTP is always omitted and never submitted. There are no field subsets, overrides, or custom field renderers. `className`, CSS, and `submitLabel` customize presentation only.
- Non-sensitive values are prefilled. Sensitive inputs are blank, masked by default, and immediately editable, even when a value is already stored. Each sensitive input has a show/hide button that changes only the native input type; it never loads a stored secret. Blank optional fields clear stored values with `null`. Password bytes are never trimmed.
- Required fields need a nonempty visible value on every submission; stored `has_value` does not bypass validation. Actual DOM values are read on submit so password-manager autofill does not depend on React change events. Controls have stable labels, names, autocomplete hints, associated errors, and first-error focus.
- `onSubmit` receives `{version, fields: {name: {value: string | null}}}` with changed values only, including `{fields:{}}` when unchanged. Resolving means success; rejecting with `CredentialFormError` selects a safe fixed message. Other errors are never rendered or logged. Pending requests disable repeat submission.

Ready means populated, not a successful login. Opening a form does not clear values or change readiness.

## styling

The component renders accessible native HTML, not a themed UI. Without application CSS it uses browser defaults. Apply your own styles under `className`; the same class is retained on the completion message. No stylesheet import is required or available.

Stable, neutral class hooks:

| area    | classes                                                                                                            |
| ------- | ------------------------------------------------------------------------------------------------------------------ |
| root    | `credential-form`, `credential-complete`                                                                           |
| fields  | `credential-field`, `credential-label`, `credential-field-name`, `credential-input-control`, `credential-input`    |
| text    | `credential-description`, `credential-hint`, `credential-error`                                                    |
| actions | `credential-primary` (submit), `credential-visibility` (show/hide), `credential-visibility-icon` (inline eye icon) |

Use native `:disabled`, `:focus-visible`, `[aria-invalid="true"]`, `[aria-busy="true"]`, and `[role="alert"]` for state styling. Preserve visible focus, label/error associations, and the supplied case of user content. Styling does not change which fields render or how values are submitted.

The hosted `vault.kernel.sh` app imports this same component and supplies its own stylesheet in `packages/vault`; it does not implement a second form.

## customer-hosted example

See [`kernel/vault-react-example`](https://github.com/kernel/vault-react-example) for a public, runnable Next.js starter. It includes the collection page, same-origin API route, one-time capability generator, production storage interface, local mock mode, and tests. Clone that repository instead of copying private monorepo files. Server code imports safe data utilities from `@onkernel/vault-react/data`, which has no React dependency or client directive. The React entry point carries `use client` for frameworks that require a client boundary.

Customer-hosted forms do not call Kernel's private hosted collection protocol. Kernel does not store your collection URL or authenticate your end users. The application owns the URL, capability lifecycle, and user-to-item mapping.

Before exposing your application's form:

1. Generate 32 random bytes on the server and base64url-encode them. Put the 43-character token in `/credentials/collect#token=<token>`; do not put it in a path, query, cookie, `localStorage`, or `sessionStorage`.
2. PUT the credential using `{type:"credential", spec:{fields:...}}`, without a collection URL. Keep the original creation input and chosen key stable for crash/retry recovery. Use ordinary item GET/PATCH from your backend; there is no need to invoke `collect` for a customer-hosted form.
3. SHA-256 hash the token and durably persist `{tokenDigest, vaultId, itemKey, itemId, expiresAt}` before sharing the URL. Store only the digest. Bind `itemId` from the PUT response so deletion/recreation at the same key cannot retarget the link.
4. On **every** read and submission, hash the bearer again and verify the mapping and application expiry. The bearer is authorization. Never accept a vault/key/item target from browser input. If the page also requires a signed-in user, enforce that session in addition to the capability and bind its user ID in the mapping.
5. For changed fields, send `PATCH /vaults/{vaultId}/items/{itemKey}` with `{type:"credential", version:<rendered version>, spec:{fields}}`. The example validates fields, blocks TOTP writes, checks same-origin PATCHes, bounds bodies at 128 KiB, and skips PATCH for an unchanged callback. A racing PATCH still fails the server version check; never retry with a newer version automatically.

For example, generate the capability with `randomBytes(32).toString('base64url')` and persist `createHash('sha256').update(token).digest('base64url')`. Deliver the complete URL through a channel appropriate for a password-reset link. Set a short expiry, revoke it after completion where practical, and let users request a new link. The page clears its fragment after a successful submission and reloads if the fragment changes.

The customer page and backend receive raw entered values. They are trusted, not isolated from customer scripts. Exclude credentials, bearer links, request bodies, authorization headers, and upstream error objects from logs, traces, analytics, and session replay. Serve the page over HTTPS with no-store and no-referrer policies. The application owns completion notifications; readiness polling is not a submission notification.

The backend sends the mapping's immutable `itemId` as `expected_item_id` on every PATCH, alongside the rendered version. Kernel rejects a replaced item with 409 and performs accepted writes by immutable ID, so deleting and reusing a key cannot redirect an old form's submission to the replacement—even when its version matches.

## development and checks

The package declares its own build and test dependencies and does not import sibling workspace test helpers or extend a root TypeScript configuration. Its source, tests, build configuration, and validation script are all inside this directory. All hosted branding lives outside this package.

```sh
cd packages/vault-react
bun install
bun run build
bun run typecheck
bun run test
# Watch the package during local development:
bun run dev
```

From the monorepo root, use `bun install --frozen-lockfile`, then run the package commands below from `packages/vault-react`. The hosted app has its own [checks](../vault/README.md#checks).

## distribution check

```sh
# Build/test a clean copy outside the workspace, then verify the packed artifact
# in isolated React 18 and React 19 Node consumers (ESM, CommonJS, and types):
bun run check:package
bun pm pack --dry-run
# Create an npm-compatible tarball without publishing:
bun pm pack --filename /tmp/onkernel-vault-react.tgz
```

`prepack` rebuilds the package. The tarball contains only `dist/`, package metadata, this README, and the package-local MIT license. React and React DOM remain external peer dependencies; the package is side-effect-free. The tarball check rejects CSS, fonts, and SVG assets. Conditional exports select matching ESM/CommonJS declaration files. CI runs the standalone package/consumer check in addition to the workspace checks. This package is MIT-licensed. Use the root export for the component and browser error class, `/data` for server-safe types/projection, with no stylesheet export. The build shares the error class across entry points and emits production-compatible JSX so the component also works in optimized consumer bundles.

Tests cover dirty/unchanged forms, native autofill reads, required secret re-entry and optional clearing, validation/accessibility, TOTP omission, stale versions, redaction, and duplicate submissions. Distribution tests also verify the absence of styles and design-system classes.

## releasing

Maintainers: after merging a version bump, publish a GitHub Release tagged `vault-react-v<version>` from `main`. Pushing the tag alone does not start the npm workflow. See [RELEASING.md](RELEASING.md) for setup and checks.

---
_Source: https://npm.io/package/@onkernel/vault-react · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
