@formbrew/react
v0.4.0
Published
Typed React hooks and accessible managed forms for Formbrew.
Downloads
228
Maintainers
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/reactImport 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.
