https://github.com/wildhoney/formikate
🪚 Lightweight form builder for React that lets you dynamically render form fields from validation schemas, manage multi-step flows, and simplify validation handling.
https://github.com/wildhoney/formikate
form form-builder form-builder-using-react formik formik-form formik-inputs formik-schema formik-validation formik-yup forms validation validation-library yup zod
Last synced: 13 days ago
JSON representation
🪚 Lightweight form builder for React that lets you dynamically render form fields from validation schemas, manage multi-step flows, and simplify validation handling.
- Host: GitHub
- URL: https://github.com/wildhoney/formikate
- Owner: Wildhoney
- Created: 2025-09-03T20:42:22.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-09-09T22:03:33.000Z (9 months ago)
- Last Synced: 2025-09-10T01:08:07.342Z (9 months ago)
- Topics: form, form-builder, form-builder-using-react, formik, formik-form, formik-inputs, formik-schema, formik-validation, formik-yup, forms, validation, validation-library, yup, zod
- Language: TypeScript
- Homepage:
- Size: 2.06 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
#
Formikate
[](https://github.com/Wildhoney/Formikate/actions/workflows/checks.yml)
Lightweight form builder for React that lets you dynamically render form fields from validation schemas, manage multi-step flows, and simplify validation handling.
**[View Live Demo](https://wildhoney.github.io/Formikate/)**
## Features
- Dynamically render form fields using [`zod`](https://github.com/colinhacks/zod) validation schemas
- Declarative multi-step forms via `useFields` configuration
- Inactive fields are automatically reset to their default values
- Steps without active fields are automatically skipped during navigation
- Per-step validation — only fields on the current step (or earlier) are validated on submit
## Getting started
Begin by defining your validation schema and field descriptors:
```tsx
import * as z from 'zod';
export const schema = z.object({
name: z.string().min(1, 'Name is required'),
address: z.string().min(1, 'Address is required'),
guest: z.boolean(),
});
export type Schema = z.infer;
export const fields = {
name: { step: 'personal' as const, validate: schema.shape.name, value: '' },
address: {
step: 'delivery' as const,
validate: schema.shape.address,
value: '',
},
guest: {
step: 'personal' as const,
validate: schema.shape.guest,
value: false,
},
};
```
Import `useForm` – it accepts all of the same [`useFormik` (`Formik`) arguments](https://formik.org/docs/api/useFormik) (except `validate`, `validationSchema`, and `initialValues` which are handled internally). Initial values are derived from each field's `value` property:
```tsx
import { useForm, useFields, Position } from 'formikate';
import { fields } from './utils';
const form = useForm({
fields,
validateOnBlur: false,
validateOnChange: false,
onSubmit(values) {
if (!form.status.progress.last)
return void form.status.navigate.to(Position.Next);
console.log('Submitting', values);
},
});
```
You can use `form` to access [all of the usual](https://formik.org/docs/api/formik#props-1) Formik properties such as `form.values` and `form.errors`.
## Defining Steps and Fields
Use `useFields` to declare the step structure and field configuration. The `step` property on each field is strongly typed — it must match one of the identifiers in the `steps` array:
```tsx
useFields(form, () => ({
steps: ['personal', 'delivery', 'review'],
fields: {
...fields,
address: {
...fields.address,
active: form.values.guest === false,
},
},
}));
```
### Config Shape
| Property | Type | Description |
| -------- | -------------------------------- | ----------------------------------------- |
| `steps` | `(string \| number \| symbol)[]` | Ordered list of step identifiers |
| `fields` | `Record` | Map of field names to their configuration |
### Field Config
| Property | Type | Description |
| ---------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `step` | `string \| number \| symbol` | Which step this field belongs to — must match one of the identifiers in `steps` |
| `validate` | `ZodType` | Zod schema used for validation |
| `value` | `unknown` | Default/reset value for the field — also used as the initial value when passed to `useForm` |
| `active` | `boolean?` | Whether the field is active (default `true`). Inactive fields are excluded from validation and reset to `value` |
### Automatic Step Skipping
Steps where all fields have `active: false` are automatically skipped during navigation:
```tsx
useFields(form, () => ({
steps: ['personal', 'delivery', 'review'],
fields: {
...fields,
address: {
...fields.address,
active: form.values.guest === false,
},
},
}));
```
When `guest` is `true`, the `address` field is inactive, so the delivery step is skipped.
## Status
After calling `useFields`, the computed state is available on `form.status`:
```tsx
form.status.empty; // boolean — true when no fields/steps are configured
form.status.field; // Record
form.status.progress; // step progression state
form.status.navigate; // navigation controls
```
### Field State
```tsx
form.status.field.name.exists(); // true if the field is active
form.status.field.name.required; // true if the Zod schema rejects `undefined`
form.status.field.name.optional; // inverse of required
```
### Progress
```tsx
form.status.progress.current; // identifier of the current step
form.status.progress.position; // zero-based index within visible steps
form.status.progress.total; // total number of visible steps
form.status.progress.first; // whether on the first visible step
form.status.progress.last; // whether on the last visible step
form.status.progress.steps; // array of { id, index } for visible steps
form.status.progress.step; // map of step id → { visible, current }
```
### Navigation
```tsx
import { Position } from 'formikate';
form.status.navigate.to(Position.Next); // go to next step
form.status.navigate.to(Position.Previous); // go to previous step
form.status.navigate.to(Position.First); // go to first step
form.status.navigate.to(Position.Last); // go to last step
form.status.navigate.to('review'); // go to a specific step by id
form.status.navigate.exists(Position.Next); // true if a next step exists
form.status.navigate.exists(Position.Previous); // true if a previous step exists
form.status.navigate.exists('review'); // true if a specific step is reachable
```
## Rendering
Use Formikate's `Form` component to provide the form to child components:
```tsx
import { Form, Position } from 'formikate';
{form.status.field.name.exists() && (
)}
{form.status.field.address.exists() && (
)}
form.status.navigate.to(Position.Previous)}
>
Back
{form.status.progress.last ? 'Submit' : 'Next'}
;
```
### Accessing Form in Child Components
Use the `useFormContext` hook in child components to access the form with properly typed `status`:
```tsx
import { useFormContext } from 'formikate';
import type { Schema } from './types';
function NameField() {
const form = useFormContext();
if (!form.status.field.name.exists()) return null;
return ;
}
```
## Empty State
When all fields are inactive, `form.status.empty` is `true`:
```tsx
{
form.status.empty ? (
No fields available
) : (
{/* ... */}
);
}
```