@labmgm/forms
v0.1.4
Published
Form primitives for MGM Laboratory — Input, Textarea, Select, Combobox, Switch, Slider, FileDropzone, Wizard, plus React Hook Form + Zod helpers.
Readme
@labmgm/forms
The full forms toolkit for MGM Laboratory.
Field primitives, React Hook Form + Zod wiring, a polished <Field> wrapper, and a <Wizard> for multi-step flows.
pnpm add @labmgm/forms react-hook-form zod@labmgm/forms supports Zod 3.25+ and Zod 4. It uses the Zod and React Hook Form
instances already installed by your application, avoiding duplicate validation runtimes.
Quick example — validated signup form
import { Form, FormProvider, useMgmForm, Field, Input } from '@labmgm/forms';
import { emailSchema, passwordSchema, z } from '@labmgm/forms/schemas';
import { Button } from '@labmgm/react';
import { toast } from '@labmgm/toast';
const schema = z.object({
email: emailSchema,
password: passwordSchema,
});
export function SignupForm() {
const form = useMgmForm(schema, { defaultValues: { email: '', password: '' } });
return (
<FormProvider {...form}>
<Form
onSubmit={form.handleSubmit(({ email }) => toast.success(`Welcome, ${email}`))}
className="max-w-md space-y-4"
>
<Field label="Email" required error={form.formState.errors.email?.message}>
<Input type="email" placeholder="[email protected]" {...form.register('email')} />
</Field>
<Field
label="Password"
required
help="≥8 chars, with upper/lower/number."
error={form.formState.errors.password?.message}
>
<Input type="password" {...form.register('password')} />
</Field>
<Button type="submit" fullWidth>
Create account
</Button>
</Form>
</FormProvider>
);
}Primitive components
| Component | Purpose |
| ---------------------------- | --------------------------------------------------------------------------- |
| Label | <label> with optional required asterisk |
| Field | Wraps Label + input + help/error, wires aria-invalid / aria-describedby |
| FieldError · FieldHelp | Sub-components for custom layouts |
| Input | Text/email/url input with optional leading / trailing slots |
| Textarea | Multi-line input |
| SearchInput | Pre-wired with search icon + clear button |
| NumberInput | Spinner with min/max/step, optional controls={false} |
| PinInput | One-time-code input (default 6 digits) |
| Checkbox · CheckboxGroup | Radix-backed, label + description slots |
| Radio · RadioGroup | Radix-backed |
| Switch | Radix-backed toggle |
| Slider | Radix-backed range |
| Select | Native-feeling Radix Select |
| Combobox | Filterable single-select (cmdk) |
| MultiSelect | Filterable multi-select with chip display |
| TagInput | Free-form tag entry — Enter/comma to add |
| FileDropzone | Drag-and-drop file picker with accept / maxSize |
| ColorPicker | Brand presets + native color picker fallback |
useMgmForm() — React Hook Form + Zod
A thin wrapper around useForm() that wires the zodResolver for you:
import { useMgmForm } from '@labmgm/forms';
import { z } from '@labmgm/forms/schemas';
const schema = z.object({ name: z.string().min(2) });
const form = useMgmForm(schema, { defaultValues: { name: '' } });
// ^? UseFormReturn<{ name: string }>The returned object has the full React Hook Form API. Validation is automatic.
Multi-step <Wizard> + <StepRail>
import { Wizard, WizardStep, StepRail, useWizard } from '@labmgm/forms';
import { Button } from '@labmgm/react';
<Wizard defaultCurrent={0}>
<WizardStep>
<Step title="Basics" />
</WizardStep>
<WizardStep>
<Step title="Files" />
</WizardStep>
<WizardStep>
<Step title="Review" />
</WizardStep>
</Wizard>;
function Step({ title }) {
const w = useWizard();
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-[200px_1fr]">
<StepRail
navigable
steps={[
{ title: 'Basics', description: 'Name and category' },
{ title: 'Files', description: 'Upload assets' },
{ title: 'Review', description: 'Confirm details' },
]}
/>
<div>
<h2 className="text-h2">{title}</h2>
<div className="mt-6 flex justify-between">
<Button variant="ghost" onClick={w.prev} disabled={w.isFirst}>
Back
</Button>
<Button onClick={w.next} disabled={w.isLast}>
{w.isLast ? 'Finish' : 'Next'}
</Button>
</div>
</div>
</div>
);
}useWizard() exposes { current, count, next, prev, goTo, isFirst, isLast, setCurrent }.
Zod schemas
import {
emailSchema,
urlSchema,
phoneSchema,
slugSchema,
passwordSchema,
nonEmptyString,
z,
} from '@labmgm/forms/schemas';
const schema = z.object({
email: emailSchema,
password: passwordSchema, // ≥8 chars, with upper/lower/number
slug: slugSchema, // lowercase-with-hyphens
url: urlSchema,
phone: phoneSchema,
name: nonEmptyString('Name'), // configurable error message
});See also
@labmgm/react— Button, Card, etc. (the form examples above use these)@labmgm/calendar— DatePicker, DateRangePicker, TimePicker@labmgm/toast— feedback on submit
License
MIT © MGM Laboratory
