Getting Started with Allem SDK Forms: Declarative Validation for React
Learn how to use @allem-sdk/forms with useForm, useField, and 9 built-in validators for declarative form management in React.

@allem-sdk/forms provides declarative form management with built-in validation. Zero dependencies, full TypeScript support, and 9 validators that cover most form scenarios.
Installation
npm install @allem-sdk/forms
Or with the meta-package:
npm install allem-sdk
useForm
The primary hook. Define your fields with initial values and validation rules:
import { useForm, required, email } from "@allem-sdk/forms";
function ContactForm() {
const form = useForm({
name: { initialValue: "", rules: [required()] },
email: { initialValue: "", rules: [required(), email()] },
});
const onSubmit = async (values) => {
await fetch("/api/contact", {
method: "POST",
body: JSON.stringify(values),
});
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<input {...form.getFieldProps("name")} placeholder="Name" />
{form.touched.name && form.errors.name && (
<span className="text-red-500">{form.errors.name}</span>
)}
<input {...form.getFieldProps("email")} placeholder="Email" />
{form.touched.email && form.errors.email && (
<span className="text-red-500">{form.errors.email}</span>
)}
<button type="submit" disabled={form.isSubmitting}>
Send
</button>
</form>
);
}
getFieldProps
The getFieldProps helper returns value, onChange, onBlur, and name — everything a standard input needs:
// This:
<input {...form.getFieldProps("email")} />
// Is equivalent to:
<input
name="email"
value={form.values.email}
onChange={(e) => form.setValue("email", e.target.value)}
onBlur={() => form.setTouched("email")}
/>
useForm Returns
| Property | Type | Description |
|---|---|---|
values |
T |
Current form values |
errors |
Partial<Record<keyof T, string>> |
Error messages per field |
touched |
Partial<Record<keyof T, boolean>> |
Which fields have been blurred |
isValid |
boolean |
True when no errors exist |
isSubmitting |
boolean |
True during async submit |
setValue |
(field, value) => void |
Set a field value |
setTouched |
(field) => void |
Mark a field as touched |
validate |
() => boolean |
Validate all fields |
handleSubmit |
(onSubmit) => handler |
Form submit handler |
reset |
() => void |
Reset to initial values |
getFieldProps |
(field) => props |
Spread props for an input |
Built-in Validators
9 validators cover the most common scenarios:
| Validator | Usage | Description |
|---|---|---|
required() |
required("Field is required") |
Must not be empty |
minLength(n) |
minLength(3, "Too short") |
Minimum string length |
maxLength(n) |
maxLength(100, "Too long") |
Maximum string length |
min(n) |
min(18, "Must be 18+") |
Minimum numeric value |
max(n) |
max(100, "Too high") |
Maximum numeric value |
pattern(regex) |
pattern(/^\d+$/, "Numbers only") |
Must match regex |
email() |
email("Invalid email") |
Valid email format |
url() |
url("Invalid URL") |
Valid URL (http/https) |
custom(fn) |
custom((v) => ...) |
Custom validation function |
All validators accept an optional custom error message as the last argument.
Custom Validators
Use custom() for validation logic that the built-in rules do not cover:
import { useForm, required, custom } from "@allem-sdk/forms";
const passwordStrength = custom<string>((value) => {
if (value.length < 8) return "Must be at least 8 characters";
if (!/[A-Z]/.test(value)) return "Must contain an uppercase letter";
if (!/[0-9]/.test(value)) return "Must contain a number";
return undefined; // valid
});
const form = useForm({
password: {
initialValue: "",
rules: [required(), passwordStrength],
},
confirmPassword: {
initialValue: "",
rules: [
required(),
custom((value) => {
if (value !== form.values.password) return "Passwords do not match";
return undefined;
}),
],
},
});
useField
A standalone hook for single-field validation. Useful when you need validation on an isolated input:
import { useField, required, email } from "@allem-sdk/forms";
function EmailInput() {
const emailField = useField({
initialValue: "",
rules: [required(), email()],
});
return (
<div>
<input
value={emailField.value}
onChange={(e) => emailField.setValue(e.target.value)}
onBlur={emailField.onBlur}
placeholder="you@example.com"
/>
{emailField.touched && emailField.error && (
<span className="text-red-500">{emailField.error}</span>
)}
</div>
);
}
useField Returns
| Property | Type | Description |
|---|---|---|
value |
T |
Current field value |
setValue |
(value: T) => void |
Set the value |
onBlur |
() => void |
Mark as touched |
error |
string | undefined |
Error message |
touched |
boolean |
Whether the field has been blurred |
Complete Registration Form
import { useForm, required, email, minLength, min, custom } from "@allem-sdk/forms";
import { Button, Input, Select, Card, CardBody, CardHeader } from "@allem-ui/react";
export default function RegistrationForm() {
const form = useForm({
name: {
initialValue: "",
rules: [required(), minLength(2, "Name must be at least 2 characters")],
},
email: {
initialValue: "",
rules: [required(), email()],
},
age: {
initialValue: 0,
rules: [required(), min(18, "Must be at least 18 years old")],
},
password: {
initialValue: "",
rules: [
required(),
minLength(8, "Password must be at least 8 characters"),
custom((v) => {
if (!/[A-Z]/.test(v)) return "Must contain an uppercase letter";
if (!/[0-9]/.test(v)) return "Must contain a number";
return undefined;
}),
],
},
});
const onSubmit = async (values) => {
const res = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
});
if (res.ok) alert("Registration successful!");
};
return (
<Card variant="outline" className="max-w-md mx-auto">
<CardHeader>Create Account</CardHeader>
<CardBody>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div>
<Input label="Full Name" {...form.getFieldProps("name")} />
{form.touched.name && form.errors.name && (
<p className="text-red-500 text-sm mt-1">{form.errors.name}</p>
)}
</div>
<div>
<Input label="Email" type="email" {...form.getFieldProps("email")} />
{form.touched.email && form.errors.email && (
<p className="text-red-500 text-sm mt-1">{form.errors.email}</p>
)}
</div>
<div>
<Input label="Age" type="number" {...form.getFieldProps("age")} />
{form.touched.age && form.errors.age && (
<p className="text-red-500 text-sm mt-1">{form.errors.age}</p>
)}
</div>
<div>
<Input label="Password" type="password" {...form.getFieldProps("password")} />
{form.touched.password && form.errors.password && (
<p className="text-red-500 text-sm mt-1">{form.errors.password}</p>
)}
</div>
<div className="flex gap-2">
<Button type="submit" disabled={form.isSubmitting} className="flex-1">
{form.isSubmitting ? "Creating..." : "Create Account"}
</Button>
<Button type="button" variant="outline" onPress={form.reset}>
Reset
</Button>
</div>
</form>
</CardBody>
</Card>
);
}
What's Next
- Explore Allem SDK Auth for authentication
- Try Allem SDK AI for AI chat hooks
- Read the full forms documentation
Enjoyed this article?
I write about building products, AI, aviation, and the journey of entrepreneurship. Follow along for more.
Keep reading

Getting Started with Allem UI Date Picker: Accessible Calendars for React
Learn how to set up @allem-ui/date-picker with Calendar, DatePicker, DateRangePicker, and TimeField components built on React Aria.

Getting Started with Allem UI Data Grid: Sortable, Filterable Tables for React
Learn how to set up @allem-ui/data-grid with TanStack Table to build sortable, filterable, and paginated data tables in React.

Getting Started with Allem UI React: Build Accessible Interfaces in Minutes
Learn how to install @allem-ui/react, set up Tailwind CSS v4, and build your first accessible interface with 30+ production-ready components.