npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@formbrew/react

v0.4.0

Published

Typed React hooks and accessible managed forms for Formbrew.

Downloads

228

Readme

@formbrew/react

Typed React hooks and an accessible managed Formbrew form. React 18.2 and React 19 are supported.

Version 0.4.0 includes the complete field set, matching slots, and the public FormbrewFields component. Integrations on 0.3.0 must upgrade before loading definitions with new field types, Text configuration, or Number settings. See the 0.4.0 release notes.

Install

npm install @formbrew/react

Import the optional shared stylesheet once in your application:

import "@formbrew/react/styles.css";

The managed form emits the same fb-* classes and CSS variables as @formbrew/js.

Managed form

import { FormbrewForm } from "@formbrew/react";
import "@formbrew/react/styles.css";

export function ContactForm() {
  return (
    <FormbrewForm
      token="YOUR_PUBLIC_TOKEN"
      metadata={() => ({ source: window.location.pathname })}
      onSuccess={(result) => console.log(result.message)}
    />
  );
}

Pass either client or token with optional baseUrl, origin, and fetch, never both. Browser requests supply Origin automatically. Server-side and non-browser requests should set the configured website origin explicitly:

<FormbrewForm token="YOUR_PUBLIC_TOKEN" origin="https://www.example.com" />

The form uses native controls and browser validation, adds checkbox-group limits and normalized confirmation matching, prevents duplicate submissions, includes the configured honeypot, and resets fields after success by default. Set resetOnSuccess={false} to preserve entered values. Caller onInput and onChange handlers are preserved; matching is refreshed after they run. Native resets clear stale confirmation errors once default values have been restored.

Date-only controls use daily steps; time and date/time controls use minute steps. Multiline Text and its confirmations render textareas. Hidden fields render first as bare inputs, and Content renders safe structured markup without an editor dependency.

Hooks

import {
  useFormbrewClient,
  useFormbrewDefinition,
  useFormbrewSubmit,
} from "@formbrew/react/hooks";

function HeadlessForm() {
  const client = useFormbrewClient({ token: "YOUR_PUBLIC_TOKEN" });
  const definition = useFormbrewDefinition({ client });
  const submission = useFormbrewSubmit({
    client,
    metadata: () => ({ source: "headless" }),
  });

  if (definition.status === "loading" || definition.status === "idle") {
    return <p>Loading...</p>;
  }

  if (definition.status === "error") {
    return <button onClick={() => void definition.reload()}>Retry</button>;
  }

  return (
    <button
      disabled={submission.status === "submitting"}
      onClick={() => void submission.submit({ email: "[email protected]" })}
    >
      {definition.definition.submitButtonLabel}
    </button>
  );
}

Definition and submission state are discriminated by status. Unknown failures are normalized to FormbrewError; errors already produced by @formbrew/js are preserved.

Slots

Every public field type and managed state can be replaced independently:

import { FormbrewForm, FormbrewTextField, type FormbrewTextFieldProps } from "@formbrew/react";

function CustomText(props: FormbrewTextFieldProps) {
  return (
    <div className="fb-field custom-text">
      <FormbrewTextField {...props} />
    </div>
  );
}

<FormbrewForm token="YOUR_PUBLIC_TOKEN" slots={{ text: CustomText }} />;

Slots are available for text, email, number, phone, website, checkbox, checkboxGroup, radioGroup, select, dateTime, hidden, confirm, content, submitButton, status, loading, and loadError. Field slots receive hydration-safe control IDs and the narrowed field definition. Confirm slots also receive their resolved target.

A field slot owns its semantic markup: preserve field.key as the control name, associate labels with the supplied IDs, and render any help element referenced by aria-describedby. Hidden slots emit bare hidden inputs; Content slots emit no named control. Checkbox/radio groups also receive optionIds. Wrapping a default primitive preserves its native constraints, multiline behavior, and help markup; keep fb-field on the outer wrapper when using the shared grid stylesheet.

Accessible default field components and their named prop types are exported from @formbrew/react/fields and the root entry.

Rendering fields in your own form

FormbrewFields and FormbrewFieldsProps are exported from the root and /fields entries. It renders a complete field array, with Hidden inputs first, using the same primitives and slots as the managed form. Supply a unique instanceId (for example React's useId()). The array itself is not filtered or changed.

import { useId } from "react";
import { FormbrewFields, type PublicFormDefinition } from "@formbrew/react";
import "@formbrew/react/styles.css";

type FieldsProps = { definition: PublicFormDefinition };

function Fields({ definition }: FieldsProps) {
  const instanceId = useId();

  return <FormbrewFields fields={definition.fields} instanceId={instanceId} />;
}

This is a rendering component, not a replacement for the managed form lifecycle. Your enclosing form owns submission, status, the honeypot, and form-level validation such as checkbox-group counts. Use collectFormbrewSubmission and synchronizeConfirmFields from @formbrew/js/form with custom controls. Prefer FormbrewForm when you want that wiring managed for you. The server always revalidates answers and discards confirmation values after matching them.

SSR and preload

All JavaScript entries can be imported without DOM globals. To avoid a client-side definition request, fetch on the server and pass the result as initialDefinition; it is authoritative and suppresses the initial fetch:

import { createFormbrewClient } from "@formbrew/js";
import { FormbrewForm } from "@formbrew/react";

const client = createFormbrewClient({
  origin: "https://www.example.com",
  token: process.env.FORMBREW_TOKEN!,
});
const definition = await client.fetchForm();

export function Page() {
  return <FormbrewForm initialDefinition={definition} token={process.env.FORMBREW_TOKEN!} />;
}

Multiple forms in the same React tree keep independent IDs and validation state. When hydrating separate React roots, use React's identifierPrefix option consistently on server and client to avoid cross-root useId collisions.

Metadata

Static metadata and callbacks are resolved at submission time. Hidden or custom controls whose names begin with _ are collected as metadata and override configured keys; the first underscore is removed.

Cancellation

Hooks expose abort(). The managed form exposes abort() and reset() through its ref. Cancellation does not produce visible error state, and changing clients or unmounting aborts stale work.

import { useRef } from "react";
import { FormbrewForm, type FormbrewFormHandle } from "@formbrew/react";

const form = useRef<FormbrewFormHandle>(null);

<FormbrewForm ref={form} token="YOUR_PUBLIC_TOKEN" />;

form.current?.abort();
form.current?.reset();

0.4.0

0.4.0 adds Date/Time, Hidden, Confirm, and Content fields and slots, multiline Text support, expanded Number validation, and the public FormbrewFields component. Full notes: Frontend 0.4.0.

0.3.0

0.3.0 adds a select slot (FormbrewSelectField, FormbrewSelectFieldProps) and requires @formbrew/[email protected], which removes the FormbrewFieldWidth type and width field property. Full notes: Frontend 0.3.0.