zod-schema-form
v1.3.0
Published
Zero-config React form generator powered by JSON Schema-flavoured definitions and Zod-friendly semantics. Point it at a schema, optionally pass theme overrides, and ship polished forms without hand-rolling inputs.
Maintainers
Readme
Zod Schema Form
Zero-config React form generator powered by JSON Schema-flavoured definitions and Zod-friendly semantics. Point it at a schema, optionally pass theme overrides, and ship polished forms without hand-rolling inputs.
- Framework agnostic: Consumes plain objects—the same schema structure you would hydrate from Zod—and renders React 18+/19 components.
- Tailwind-friendly: Ships utility-powered class names, so styles flow automatically when Tailwind scans the bundle.
- Themable: CSS variables + theme presets provide instant visual customization with a single prop or even per-token overrides.
- Type-safe interactions: TypeScript definitions ensure strong typing for schema, form data, handlers, and theming.
Installation & usage
Install the package alongside your React app:
npm install zod-schema-formImport the component, your schema, and (optionally) a theme:
import ZodSchemaForm, {
SchemaArray,
lightTheme,
darkTheme,
ThemeOverrides
} from 'zod-schema-form'
import 'zod-schema-form/styles.css'
const userSchema = [
['name', { type: 'string', label: 'Full Name', required: true }],
['email', { type: 'string', label: 'Email', required: true }],
['age', { type: 'number', label: 'Age', minimum: 0 }],
['isAdmin', { type: 'boolean', label: 'Has admin access?' }]
] satisfies SchemaArray
type UserForm = {
name: string
email: string
age: number
isAdmin: boolean
}
const overrides: ThemeOverrides = {
buttonPrimaryBackground: '#a855f7',
focusRing: 'rgba(168, 85, 247, 0.45)'
}
export const UserFormCard = () => (
<ZodSchemaForm<UserForm>
schema={userSchema}
layout='column'
size='medium'
onSubmit={(data) => console.log('Submit', data)}
onValueChange={(field, value) => console.log(`${field} →`, value)}
theme={darkTheme}
themeOverrides={overrides}
/>
)Styling options
- Bundled CSS (zero-config): Import
zod-schema-form/styles.cssonce in your app to get the default spacing, layout, and theme variable wiring—perfect for projects without Tailwind. All fallbacks are scoped to.jsf-formso they won't leak into the rest of your styles. - Tailwind-aware: If your project already uses Tailwind, ensure the library's files are included in the scan list so utilities resolve automatically:
export default {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/zod-schema-form/dist/**/*.{js,cjs,mjs}',
'./node_modules/zod-schema-form/src/**/*.{ts,tsx}'
],
theme: {
extend: {}
}
}Schema definition walkthrough
Schemas are plain arrays of [fieldName, SchemaProperty]. A SchemaProperty loosely mirrors Zod field options with additional JSON Schema conveniences. Here’s the process to author a schema:
- Enumerate fields – use tuple entries so TypeScript can infer exact keys.
- Assign core metadata –
type,label,description,required, etc. - Add constraints – numeric bounds (
minimum,maximum,exclusiveMinimum,exclusiveMaximum), string limits (minLength,maxLength), or enums (enum,enumMultiple). - Shape nested data – set
type: 'object'with apropertiesmap, ortype: 'array'with anitemsschema. Both support recursion. - Handle nullable values – use
nullable: trueor include{ type: 'null' }inanyOfif your Zod schema allows nulls. - Wire defaults & sensitivity –
default,sensitive(renders password inputs), custom labels, placeholder text, and descriptions.
Primitive fields
['username', {
type: 'string',
label: 'Username',
minLength: 3,
maxLength: 24,
description: 'Used for signing in'
}]Numeric fields
['yearsExperience', {
type: 'number',
minimum: 0,
maximum: 40,
label: 'Years of experience'
}]Enum selections
['role', {
type: 'string',
enum: ['Viewer', 'Editor', 'Owner'],
label: 'Workspace role'
}]Set enumMultiple: true to render checkbox chips for multi-select arrays.
Arrays
['favoriteLanguages', {
type: 'array',
label: 'Preferred languages',
items: {
type: 'string',
enum: ['TypeScript', 'Rust', 'Go', 'Python']
},
minItems: 1,
maxItems: 5
}]Arrays can also contain structured objects. Each array item renders as its own mini-form, inheriting the same schema capabilities (nested arrays, enums, nullable fields, etc.):
['embeddings', {
type: 'array',
label: 'Embeddings',
items: {
type: 'object',
properties: {
vector: {
type: 'array',
label: 'Vector values',
items: { type: 'number' }
},
text: {
type: 'string',
label: 'Source text',
minLength: 2,
maxLength: 1000
}
}
}
}]Arrays support nullable values, default lists, and numeric/string/boolean primitive types via the nested items schema.
Objects / records
['address', {
type: 'object',
label: 'Mailing address',
properties: {
street: { type: 'string', label: 'Street', required: true },
city: { type: 'string', label: 'City', required: true },
zipCode: { type: 'string', label: 'Postal code', required: true }
}
}]objectKeyType & objectValueType let you drive dictionary-style inputs when you want dynamic key/value pairs.
Nullable & anyOf
['middleName', {
type: 'string',
label: 'Middle name',
nullable: true
}]
['status', {
anyOf: [
{ type: 'string', enum: ['active', 'paused'] },
{ type: 'null' }
],
label: 'Account status'
}]Handling changes
onValueChange receives three arguments: (fieldName, nextValue, allFormData). Use it to drive live previews or derived state.
Theme system
Styling is powered by CSS variables to keep things framework-agnostic. The form computes an inline style object based on three layers:
- Default tokens (
defaultThemeVariables) – baseline palette matching the light theme preset. themeprop – pass any object that satisfiesThemeVariables(e.g.lightTheme,darkTheme, or your custom map). These overwrite the defaults.themeOverridesprop – partial overrides applied last so you can tweak individual tokens without cloning the entire theme.
Available tokens
| Token | CSS variable | Description |
| --- | --- | --- |
| background | --jsf-background | Card background |
| border | --jsf-border | Card border color |
| shadow | --jsf-shadow | Box shadow value |
| borderRadius | --jsf-border-radius | Corner radius applied to form, inputs, buttons |
| fieldBorder, fieldBorderActive, fieldBackground, fieldText, fieldPlaceholder | --jsf-field-border, --jsf-field-border-active, --jsf-field-background, --jsf-field-text, --jsf-field-placeholder | Input visuals |
| label, description, helper | --jsf-label, --jsf-description, --jsf-helper | Text colors for metadata |
| focusRing | --jsf-focus-ring | Focus outline color |
| checkboxBorder, checkboxBackground, checkboxAccent, checkboxLabel | --jsf-checkbox-border, --jsf-checkbox-background, --jsf-checkbox-accent, --jsf-checkbox-label | Checkbox cluster |
| buttonPrimary*, buttonSecondary*, buttonGhost* | --jsf-button-primary-*, --jsf-button-secondary-*, --jsf-button-ghost-* | Button variants |
| danger* | --jsf-danger-* | Danger/destructive actions |
| chip* | --jsf-chip-* | Reserved for tag-style UI (arrays/enums) |
The exported presets include:
lightTheme– bright default palette (also exported asdefaultThemeVariables).darkTheme– deep blue-inspired aesthetic designed for night mode.themes– map of{ light, dark }for convenience.
Custom themes
import { ThemeVariables } from 'zod-schema-form'
const sunsetTheme: ThemeVariables = {
...lightTheme,
background: '#fff7ed',
border: '#fed7aa',
buttonPrimaryBackground: '#f97316',
buttonPrimaryHoverBackground: '#ea580c',
focusRing: 'rgba(249, 115, 22, 0.35)'
}
<ZodSchemaForm schema={schema} theme={sunsetTheme} />Demo & development
Clone the repo and run the demo playground:
npm install
npm run build # builds the library
cd demo
npm install
npm run devThe demo showcases the same schema and theme props described above, including a live light/dark toggle.
API surface
<ZodSchemaForm />
| Prop | Type | Description |
| --- | --- | --- |
| schema | SchemaArray | Required field definitions |
| initialValues | FormDataObject | Prefill form state |
| layout | 'column' \| 'row' | Layout orientation |
| size | 'small' \| 'medium' \| 'large' | Input/button sizing |
| onSubmit | (data) => void | Called with typed form data |
| onValueChange | (field, value, allData) => void | Field-level change handler |
| isSubmitting | boolean | Disables submit button + adds aria-busy |
| submitButtonText | string | Customize button label |
| className | string | Extra classes appended to form container |
| theme | ThemeVariables | Full theme map (defaults to lightTheme) |
| themeOverrides | ThemeOverrides | Partial override for specific tokens |
Utilities
All exported from the package root:
sizeConfigs,rowGapBySize,columnSpacingBySize,buttonVariants,getFormClassNamethemeVariableMap,defaultThemeVariables,lightTheme,darkTheme,themes- Types:
SchemaArray,SchemaField,SchemaProperty,FormDataObject,ThemeVariables,ThemeOverrides,ThemeVariableKey,ZodSchemaFormProps
License
MIT © 2025 Ben Goodman
