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

@kodeme-io/next-core-forms

v0.8.4

Published

Dynamic form builder and validation for Next.js applications

Readme

@kodeme-io/next-core-forms

Dynamic form builder and validation for Next.js applications with TypeScript support and Zod integration.

🚀 Features

  • 📝 Dynamic Form Generation - Build forms from JSON definitions
  • ✅ Powerful Validation - Zod-based validation with custom rules
  • 🔄 Array Fields - Dynamic field lists with add/remove functionality
  • 🎯 Cross-Field Validation - Validate relationships between multiple fields
  • 🧭 Form Wizard - Multi-step forms with progress tracking
  • 🎨 Flexible Components - Pre-built form components and layouts
  • 🔄 Conditional Fields - Show/hide fields based on user input
  • 📱 Mobile-First - Responsive design optimized for all devices
  • ♿ Accessible - WCAG compliant with proper ARIA labels
  • 🔧 TypeScript Native - Full type safety and IntelliSense support
  • ⚡ Performance Optimized - Cached validation and efficient rendering

📦 Installation

npm install @kodeme-io/next-core-forms
# or
yarn add @kodeme-io/next-core-forms
# or
pnpm add @kodeme-io/next-core-forms

Peer Dependencies

npm install react react-hook-form zod

🎯 Quick Start

Basic Form Example

'use client'

import { DynamicForm, FormDefinition } from '@kodeme-io/next-core-forms'
import { z } from 'zod'

const registrationForm: FormDefinition = {
  id: 'registration',
  title: 'User Registration',
  description: 'Create your account',
  sections: [
    {
      id: 'personal-info',
      title: 'Personal Information',
      fields: [
        {
          name: 'firstName',
          label: 'First Name',
          type: 'text',
          required: true,
          placeholder: 'Enter your first name',
          validation: z.string().min(2, 'Must be at least 2 characters')
        },
        {
          name: 'email',
          label: 'Email Address',
          type: 'email',
          required: true,
          validation: z.string().email('Invalid email address')
        },
        {
          name: 'password',
          label: 'Password',
          type: 'password',
          required: true,
          validation: z.string().min(8, 'Password must be at least 8 characters')
        }
      ]
    }
  ],
  submitLabel: 'Register',
  layout: 'vertical'
}

export default function RegistrationPage() {
  const handleSubmit = async (data: any) => {
    console.log('Form submitted:', data)
    // Handle form submission
  }

  return (
    <div className="max-w-md mx-auto">
      <DynamicForm form={registrationForm} onSubmit={handleSubmit} />
    </div>
  )
}

📚 Core Concepts

Form Definition Structure

interface FormDefinition {
  id: string                    // Unique form identifier
  title: string                 // Form title
  description?: string          // Optional description
  sections: FormSection[]       // Form sections
  submitLabel?: string          // Submit button text
  cancelLabel?: string          // Cancel button text
  layout?: 'vertical' | 'horizontal' | 'grid'  // Layout style
}

Field Types

Supported field types:

  • text - Single line text input
  • email - Email input with validation
  • number - Numeric input
  • tel - Telephone number input
  • url - URL input
  • password - Password input
  • textarea - Multi-line text input
  • select - Single select dropdown
  • multiselect - Multi-select dropdown
  • checkbox - Single checkbox
  • radio - Radio button group
  • date - Date picker
  • time - Time picker
  • datetime - Date and time picker
  • file - File upload
  • array - Dynamic field lists with add/remove functionality
  • custom - Custom field component

Field Definition

interface FieldDefinition {
  name: string                  // Field name (form data key)
  label: string                 // Display label
  type: FieldType              // Field type
  placeholder?: string         // Placeholder text
  defaultValue?: any          // Default value
  required?: boolean           // Required field
  options?: FieldOption[]     // Options for select/radio
  validation?: ZodType       // Zod validation schema
  helperText?: string         // Help text
  disabled?: boolean          // Disabled state
  hidden?: boolean            // Hidden field
  dependsOn?: {               // Conditional display
    field: string
    value: any
    condition?: 'equals' | 'not_equals' | 'contains'
  }
  arrayConfig?: ArrayFieldConfig // Configuration for array fields
}

interface ArrayFieldConfig {
  minItems?: number           // Minimum number of items
  maxItems?: number           // Maximum number of items
  itemLabel: string           // Label for each item
  addLabel?: string           // Add button label
  removeLabel?: string        // Remove button label
  canReorder?: boolean        // Allow reordering items
  fields: FieldDefinition[]   // Field definitions for array items
}

🎨 Components

DynamicForm

Main component for rendering forms from definitions.

<DynamicForm
  form={formDefinition}
  onSubmit={handleSubmit}
  onSubmitSuccess={handleSuccess}
  onSubmitError={handleError}
  className="custom-form"
  disabled={false}
/>

Form Components

Lower-level components for custom form building:

import {
  FormProvider,
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage
} from '@kodeme-io/next-core-forms'

function CustomForm() {
  const form = useForm({
    defaultValues: { name: '', email: '' }
  })

  return (
    <FormProvider {...form}>
      <FormField
        name="name"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Name</FormLabel>
            <FormControl>
              <input {...field} />
            </FormControl>
            <FormMessage />
          </FormItem>
        )}
      />
    </FormProvider>
  )
}

FormSection

Section component with collapsible support:

<FormSection
  section={sectionDefinition}
  defaultCollapsed={false}
  onToggle={(collapsed) => console.log(collapsed)}
>
  {/* Custom section content */}
</FormSection>

✅ Validation

Built-in Validation

Using Zod for powerful validation:

import { z } from 'zod'

const validationSchema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Must contain uppercase letter')
    .regex(/[0-9]/, 'Must contain number'),
  age: z.number().min(18, 'Must be 18 or older'),
  terms: z.boolean().refine(val => val === true, 'Must accept terms')
})

const field: FieldDefinition = {
  name: 'email',
  label: 'Email',
  type: 'email',
  required: true,
  validation: validationSchema.shape.email
}

Custom Validation

Create custom validation rules:

const customValidation = z.string().refine(
  (val) => !val.includes('spam'),
  'Email cannot contain "spam"'
)

const field: FieldDefinition = {
  name: 'customEmail',
  label: 'Email',
  type: 'email',
  validation: customValidation
}

Async Validation

For server-side validation:

const asyncValidation = z.string().refine(
  async (email) => {
    const response = await fetch(`/api/check-email?email=${email}`)
    const data = await response.json()
    return !data.exists
  },
  'Email already exists'
)

🔄 Array Fields

Dynamic field lists with add/remove functionality:

const arrayFieldForm: FormDefinition = {
  id: 'array-form',
  title: 'Array Field Example',
  sections: [
    {
      id: 'phone-numbers',
      title: 'Phone Numbers',
      fields: [
        {
          name: 'phoneNumbers',
          label: 'Phone Numbers',
          type: 'array',
          required: true,
          arrayConfig: {
            minItems: 1,
            maxItems: 5,
            itemLabel: 'Phone Number',
            addLabel: 'Add Phone',
            removeLabel: 'Remove',
            canReorder: true,
            fields: [
              {
                name: 'type',
                label: 'Type',
                type: 'select',
                required: true,
                options: [
                  { label: 'Mobile', value: 'mobile' },
                  { label: 'Home', value: 'home' },
                  { label: 'Work', value: 'work' }
                ]
              },
              {
                name: 'number',
                label: 'Phone Number',
                type: 'tel',
                required: true,
                validation: z.string().regex(/^[0-9+\-\s()]+$/, 'Invalid phone number')
              }
            ]
          },
          validation: z.array(z.object({
            type: z.enum(['mobile', 'home', 'work']),
            number: z.string().regex(/^[0-9+\-\s()]+$/)
          })).min(1, 'At least one phone number required')
        }
      ]
    }
  ]
}

🎯 Cross-Field Validation

Validate relationships between multiple fields:

import { commonCrossFieldRules } from '@kodeme-io/next-core-forms'

const crossFieldForm: FormDefinition = {
  id: 'cross-field-form',
  title: 'Cross-Field Validation Example',
  sections: [
    // ... sections with password and confirmPassword fields
    // ... sections with startDate and endDate fields
  ],
  crossFieldRules: [
    commonCrossFieldRules.passwordMatch('password', 'confirmPassword'),
    commonCrossFieldRules.dateRange('startDate', 'endDate'),
    commonCrossFieldRules.minAge('birthDate', 18),
    {
      name: 'customRule',
      message: 'Custom validation message',
      validate: (formData) => {
        // Return true if valid, false or string if invalid
        return formData.field1 !== formData.field2
      }
    }
  ]
}

🧭 Form Wizard

Multi-step forms with progress tracking:

import { FormWizard } from '@kodeme-io/next-core-forms'

export default function MultiStepForm() {
  return (
    <FormWizard
      form={multiStepFormDefinition}
      onStepChange={(stepIndex, stepData) => {
        console.log(`Step ${stepIndex + 1}:`, stepData)
      }}
      onComplete={() => {
        console.log('Wizard completed')
      }}
    />
  )
}

🔄 Conditional Fields

Show/hide fields based on other field values:

const formWithConditionalFields: FormDefinition = {
  id: 'conditional-form',
  title: 'Conditional Form',
  sections: [
    {
      id: 'main',
      title: 'Main Section',
      fields: [
        {
          name: 'hasAccount',
          label: 'Do you have an account?',
          type: 'radio',
          options: [
            { label: 'Yes', value: 'yes' },
            { label: 'No', value: 'no' }
          ],
          required: true
        },
        {
          name: 'email',
          label: 'Email Address',
          type: 'email',
          required: true,
          validation: z.string().email(),
          dependsOn: {
            field: 'hasAccount',
            value: 'yes',
            condition: 'equals'
          }
        },
        {
          name: 'newEmail',
          label: 'New Email Address',
          type: 'email',
          required: true,
          validation: z.string().email(),
          dependsOn: {
            field: 'hasAccount',
            value: 'no',
            condition: 'equals'
          }
        }
      ]
    }
  ]
}

🎯 Advanced Usage

Multi-Step Forms

const multiStepForm: FormDefinition = {
  id: 'multi-step',
  title: 'Multi-Step Registration',
  sections: [
    {
      id: 'step1',
      title: 'Personal Information',
      fields: [
        { name: 'firstName', label: 'First Name', type: 'text', required: true },
        { name: 'lastName', label: 'Last Name', type: 'text', required: true }
      ],
      collapsible: true,
      defaultCollapsed: false
    },
    {
      id: 'step2',
      title: 'Contact Information',
      fields: [
        { name: 'email', label: 'Email', type: 'email', required: true },
        { name: 'phone', label: 'Phone', type: 'tel', required: true }
      ],
      collapsible: true,
      defaultCollapsed: true
    },
    {
      id: 'step3',
      title: 'Preferences',
      fields: [
        { name: 'newsletter', label: 'Subscribe to newsletter', type: 'checkbox' },
        { name: 'notifications', label: 'Enable notifications', type: 'checkbox' }
      ],
      collapsible: true,
      defaultCollapsed: true
    }
  ]
}

Form with File Upload

const fileUploadForm: FormDefinition = {
  id: 'upload-form',
  title: 'File Upload',
  sections: [
    {
      id: 'upload',
      title: 'Upload Document',
      fields: [
        {
          name: 'document',
          label: 'Document (PDF, DOC, DOCX)',
          type: 'file',
          required: true,
          validation: z.instanceof(File).refine(
            (file) => file.size <= 10 * 1024 * 1024, // 10MB
            'File must be smaller than 10MB'
          ).refine(
            (file) => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.type),
            'Invalid file type'
          )
        }
      ]
    }
  ]
}

🧪 Testing

The package includes comprehensive test coverage:

# Run tests
npm test

# Run with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

Testing Custom Forms

import { render, screen, fireEvent } from '@testing-library/react'
import { DynamicForm } from '@kodeme-io/next-core-forms'

test('form submission', async () => {
  const handleSubmit = jest.fn()

  render(<DynamicForm form={testForm} onSubmit={handleSubmit} />)

  fireEvent.change(screen.getByLabelText('Name'), {
    target: { value: 'John Doe' }
  })

  fireEvent.click(screen.getByText('Submit'))

  await waitFor(() => {
    expect(handleSubmit).toHaveBeenCalledWith({ name: 'John Doe' })
  })
})

🔧 API Reference

Types

// Core types
export type FieldType = 'text' | 'email' | 'number' | 'tel' | 'url' | 'password' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'date' | 'time' | 'datetime' | 'file' | 'custom'

export interface FieldOption {
  label: string
  value: string | number
  disabled?: boolean
}

export interface FormState<T = any> {
  values: T
  errors: Record<string, string>
  touched: Record<string, boolean>
  isDirty: boolean
  isSubmitting: boolean
  isValid: boolean
}

// Component props
export interface DynamicFormProps {
  form: FormDefinition
  onSubmit: (data: any) => void | Promise<void>
  onSubmitSuccess?: (data: any) => void
  onSubmitError?: (error: Error) => void
  className?: string
  disabled?: boolean
  defaultValues?: Record<string, any>
}

Utilities

// Validation utilities
export function validateField(field: FieldDefinition, value: any): ValidationResult
export function validateForm(form: FormDefinition, data: any, partial?: boolean): FormValidationResult
export function createFormValidator(schema: ZodSchema): ZodSchema

// Form utilities
export function getDefaultValues(form: FormDefinition): Record<string, any>
export function getFormData(form: FormDefinition): any
export function resetForm(form: FormDefinition): void

🔍 Troubleshooting

Common Issues

  1. Validation Not Working

    • Ensure Zod schema is properly configured
    • Check that validation is assigned to the correct field
    • Verify field names match form data keys
  2. Conditional Fields Not Showing

    • Check the dependsOn configuration
    • Ensure the dependent field has the correct value
    • Verify the condition logic
  3. Form Not Submitting

    • Check if form is valid (isValid state)
    • Ensure onSubmit handler is properly defined
    • Check for validation errors

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Submit a pull request

📄 License

MIT License - see LICENSE file for details

🔗 Related Packages

📞 Support