Type-safe, high-performance React form state and input components. Utilized by the OpenSite Semantic UI website platform
FormEngineβ declarative form component with built-in API integration- Field-level reactivity via
@legendapp/state/react - Built-in input library (text, select, date, time, upload, rich text)
- Tree-shakable subpath exports (
/core,/inputs,/validation,/upload,/integration) - Validation rules and utilities (sync + async)
- Valibot adapter in a separate entrypoint (
/validation/valibot) - Tailwind token-based default UI aligned with ShadCN interaction patterns
pnpm add @page-speed/forms
# or
npm install @page-speed/formsreact >= 16.8.0react-dom >= 16.8.0
FormEngine is the recommended entry point for most use cases. It provides a declarative API for rendering forms with built-in API integration, validation, file uploads, and styling.
import * as React from "react";
import {
FormEngine,
type FormFieldConfig,
} from "@page-speed/forms/integration";
const fields: FormFieldConfig[] = [
{
name: "full_name",
type: "text",
label: "Full Name",
required: true,
placeholder: "Your name",
columnSpan: 12,
},
{
name: "email",
type: "email",
label: "Email",
required: true,
placeholder: "you@example.com",
columnSpan: 6,
},
{
name: "phone",
type: "tel",
label: "Phone",
columnSpan: 6,
},
{
name: "content",
type: "textarea",
label: "Message",
required: true,
columnSpan: 12,
},
];
export function ContactForm() {
return (
<FormEngine
api={{
endpoint: "/api/contact",
method: "post",
submissionConfig: { behavior: "showConfirmation" },
}}
fields={fields}
successMessage="Thanks for reaching out!"
formLayoutSettings={{
submitButtonSetup: {
submitLabel: "Send Message",
},
}}
/>
);
}| Prop | Type | Description |
|---|---|---|
api |
PageSpeedFormConfig |
API endpoint and submission configuration |
fields |
FormFieldConfig[] |
Array of field definitions |
formLayoutSettings |
FormEngineLayoutSettings |
Layout, style, and submit button settings |
successMessage |
ReactNode |
Message shown after successful submission |
onSubmit |
(values) => void | Promise<void> |
Custom submit handler |
onSuccess |
(data) => void |
Called after successful submission |
onError |
(error) => void |
Called when submission fails |
resetOnSuccess |
boolean |
Reset form after success (default: true) |
Each field in the fields array supports:
interface FormFieldConfig {
name: string;
type: "text" | "email" | "tel" | "textarea" | "select" | "multiselect" |
"date" | "daterange" | "time" | "file" | "checkbox" | "radio";
label?: string;
placeholder?: string;
required?: boolean;
columnSpan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
className?: string;
options?: { label: string; value: string }[]; // For select/multiselect/radio
// File-specific props
accept?: string;
maxFiles?: number;
maxFileSize?: number;
}Multi-column grid with a submit button below the fields:
<FormEngine
fields={fields}
formLayoutSettings={{
formLayout: "standard",
submitButtonSetup: {
submitLabel: "Submit",
submitVariant: "default", // | "destructive" | "outline" | "secondary" | "ghost" | "link"
},
styleRules: {
formContainer: "max-w-2xl mx-auto",
fieldsContainer: "gap-6",
formClassName: "space-y-4",
},
}}
/>Inline input with submit button (e.g., newsletter signup):
<FormEngine
fields={[{ name: "email", type: "email", label: "Email", required: true }]}
formLayoutSettings={{
formLayout: "button-group",
buttonGroupSetup: {
size: "lg", // | "xs" | "sm" | "default"
submitLabel: "Subscribe",
submitVariant: "default",
},
}}
/>For block/component libraries that provide default configurations:
import {
FormEngine,
type FormEngineSetup,
type FormFieldConfig,
type FormEngineStyleRules,
} from "@page-speed/forms/integration";
const defaultFields: FormFieldConfig[] = [
{ name: "email", type: "email", label: "Email", required: true },
];
const defaultStyleRules: FormEngineStyleRules = {
formClassName: "space-y-6",
};
// Consumer passes setup, component provides defaults
function ContactBlock({ formEngineSetup }: { formEngineSetup?: FormEngineSetup }) {
return (
<FormEngine
formEngineSetup={formEngineSetup}
defaultFields={defaultFields}
defaultStyleRules={defaultStyleRules}
/>
);
}@page-speed/forms
useForm,useField,Form,Field,FormContext- Core form/types interfaces
@page-speed/forms/integration
FormEngine,FormEngineSetup,FormEnginePropsFormFieldConfig,FormEngineStyleRules,FormEngineLayoutSettingsDynamicFormField,useContactForm,useFileUpload
@page-speed/forms/inputs
TextInput,TextArea,Checkbox,CheckboxGroup,RadioSelect,MultiSelect,DatePicker,DateRangePicker,TimePickerFileInput
@page-speed/forms/validation@page-speed/forms/validation/rules@page-speed/forms/validation/utils@page-speed/forms/validation/valibot
@page-speed/forms/upload
TimePicker uses a native input[type="time"] UX internally.
- Accepts controlled values in
HH:mm(24-hour) orh:mm AM/PM(12-hour) - Emits
HH:mmwhenuse24Houristrue - Emits
h:mm AM/PMwhenuse24Hourisfalse
- Calendar popovers close on outside click
- Compact month/day layout using tokenized Tailwind classes
DateRangePickerrenders two months and highlights endpoints + in-range dates
- Close on outside click
- Search support
- Option groups
- Selected options inside the menu use accent highlight styles
This library ships with Tailwind utility classes and semantic token class names.
- Inputs/triggers are transparent shells with semantic borders/rings
- Fields with values (text-like controls) use
ring-2 ring-primary - Error states use destructive border/ring
- Dropdown selected rows use muted backgrounds
interface FormEngineStyleRules {
formContainer?: string; // Wrapper around <form>
fieldsContainer?: string; // Grid wrapper for fields
fieldClassName?: string; // Fallback className for fields
formClassName?: string; // Applied to <form> element
successMessageClassName?: string;
errorMessageClassName?: string;
}Text-like controls apply autofill reset classes to avoid browser-injected background/text colors breaking your theme contrast.
See INPUT_AUTOFILL_RESET_CLASSES in src/utils.ts.
Ensure your app defines semantic tokens used in classes such as:
background,foreground,border,input,ringprimary,primary-foregroundmuted,muted-foregrounddestructive,destructive-foregroundpopover,popover-foregroundcard,card-foreground
For complete styling guidance, see docs/STYLES.md.
For custom form implementations, the lower-level useForm, Form, and Field APIs are available:
import { Form, Field, useForm } from "@page-speed/forms";
import { TextInput } from "@page-speed/forms/inputs";
function CustomForm() {
const form = useForm({
initialValues: { email: "" },
validationSchema: {
email: (value) => (!value ? "Required" : undefined),
},
onSubmit: async (values) => {
console.log(values);
},
});
return (
<Form form={form}>
<Field name="email" label="Email">
{({ field, meta }) => (
<TextInput
{...field}
error={Boolean(meta.touched && meta.error)}
/>
)}
</Field>
<button type="submit">Submit</button>
</Form>
);
}Use built-in rules:
required,email,url,phoneminLength,maxLength,min,maxpattern,matches,oneOfcreditCard,postalCode,alpha,alphanumeric,numeric,integercompose
Use utilities from /validation/utils:
debounce,asyncValidator,crossFieldValidator,whensetErrorMessages,getErrorMessage,resetErrorMessages
FileInput and FormEngine support validation, drag/drop, preview, and crop workflows.
For full two-phase upload patterns and serializer usage, see:
docs/FILE_UPLOADS.md@page-speed/forms/integration
pnpm test:ci
pnpm build
pnpm type-checkMIT. See LICENSE.
