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

@liam-michel/validated-form

v0.2.1

Published

A type-safe form library built on React Hook Form and Zod

Readme

@liam-michel/validated-form

CI License: MIT npm version npm downloads TypeScript

A type-safe, schema-driven form library for React. Define a Zod schema and get validated, fully-typed form fields out of the box — built on React Hook Form and Radix UI.

Installation

npm install @liam-michel/validated-form

Peer dependencies

You'll also need these in your project:

npm install react react-dom react-hook-form zod @hookform/resolvers \
  @radix-ui/react-checkbox @radix-ui/react-select @radix-ui/react-slider \
  @radix-ui/react-popover cmdk react-day-picker date-fns lucide-react \
  clsx tailwind-merge

The library uses Tailwind CSS class names. Make sure your project has Tailwind configured.

Quick start

import {
  ValidatedForm,
  TextField,
  NumberField,
  CheckboxField,
} from '@liam-michel/validated-form';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  age: z.number().min(1, 'Age is required'),
  agree: z.boolean().refine((v) => v, 'You must agree'),
});

function MyForm() {
  return (
    <ValidatedForm
      schema={schema}
      defaultValues={{ name: '', age: 0, agree: false }}
      onSubmit={async (data) => {
        // data is fully typed as { name: string; age: number; agree: boolean }
        console.log(data);
      }}
    >
      <TextField<typeof schema>
        name="name"
        label="Name"
        placeholder="Enter your name"
      />
      <NumberField<typeof schema> name="age" label="Age" />
      <CheckboxField<typeof schema> name="agree" label="I agree to the terms" />
    </ValidatedForm>
  );
}

createForm — schema-bound factory

For a cleaner API without manual generics, use createForm to generate a full set of typed components from a single schema:

import { createForm } from '@liam-michel/validated-form';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  bio: z.string(),
  role: z.enum(['admin', 'user', 'viewer']),
});

const { Form, TextField, TextAreaField, SelectField } = createForm(schema);

function ProfileForm() {
  return (
    <Form
      onSubmit={async (data) => console.log(data)}
      defaultValues={{ email: '', bio: '', role: 'user' }}
    >
      <TextField name="email" label="Email" />
      <TextAreaField
        name="bio"
        label="Bio"
        placeholder="Tell us about yourself"
      />
      <SelectField
        name="role"
        label="Role"
        options={[
          { value: 'admin', label: 'Admin' },
          { value: 'user', label: 'User' },
          { value: 'viewer', label: 'Viewer' },
        ]}
      />
    </Form>
  );
}

useValidatedForm — external form control

Use the useValidatedForm hook when you need access to the form instance outside of the <ValidatedForm> component (e.g. for programmatic control, multi-step forms, or watching values):

import {
  ValidatedForm,
  TextField,
  useValidatedForm,
} from '@liam-michel/validated-form';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1),
});

function ControlledForm() {
  const form = useValidatedForm({
    schema,
    defaultValues: { name: '' },
  });

  return (
    <ValidatedForm
      schema={schema}
      form={form}
      onSubmit={async (data) => console.log(data)}
    >
      <TextField<typeof schema> name="name" label="Name" />
    </ValidatedForm>
  );
}

Field components

All fields share these common props:

| Prop | Type | Description | | ------------- | --------------------- | --------------------------------------------------- | | name | typed key from schema | The field name (type-safe based on your Zod schema) | | label | string | Label text displayed above the field | | description | string? | Helper text displayed below the field | | placeholder | string? | Placeholder text | | disabled | boolean? | Disable the field | | className | string? | Additional CSS class for the wrapper | | showReset | boolean? | Show a reset button to restore the default value |

Available fields

| Component | Schema type | Extra props | | ------------------ | --------------------- | -------------------------------------------------------------- | | TextField | z.string() | type?: HTMLInputTypeAttribute (e.g. "password", "email") | | TextAreaField | z.string() | — | | NumberField | z.number() | — | | CheckboxField | z.boolean() | — | | SelectField | z.enum(...) | options: { value, label }[] | | SliderField | z.number() | min?, max?, step? | | DateField | z.date() | — | | MultiSelectField | z.array(z.string()) | options: { value, label }[] |

ValidatedForm props

| Prop | Type | Description | | --------------- | ---------------------------------- | -------------------------------------------------------- | | schema | ZodType | Zod schema for validation | | onSubmit | (data: Output) => Promise<void> | Submit handler (receives validated & typed data) | | defaultValues | DefaultValues? | Initial form values | | form | UseFormReturn? | External form instance from useValidatedForm | | submitLabel | string? | Custom label for the submit button (default: "Submit") | | hideSubmit | boolean? | Hide the default submit button | | renderSubmit | (state) => ReactNode | Custom submit button renderer | | renderError | (message) => ReactNode | Custom root error renderer | | children | ReactNode \| (form) => ReactNode | Fields, or a render function receiving the form instance |

Error handling

When onSubmit throws, errors are handled automatically:

  • Field-level errors: Throw { data: { fieldErrors: { fieldName: "message" } } } to set errors on specific fields.
  • Root-level errors: Any other thrown error displays its message as a form-level error.
<ValidatedForm
  schema={schema}
  onSubmit={async (data) => {
    const res = await api.createUser(data);
    if (!res.ok) {
      // Field-level errors
      throw { data: { fieldErrors: { email: 'Email already taken' } } };
    }
  }}
>
  {/* ... */}
</ValidatedForm>

Type helpers

The library exports type helpers to extract field names by type from a schema:

import type {
  StringFieldsOf,
  NumberFieldsOf,
  BooleanFieldsOf,
  DateFieldsOf,
  SelectFieldsOf,
} from '@liam-michel/validated-form';

These are used internally by the field components and can be useful if you're building custom fields.

License

MIT