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 🙏

© 2025 – Pkg Stats / Ryan Hefner

zodsform

v0.2.4

Published

Generador de formularios dinámicos con validación automática usando Zod

Readme

ZodsForm 🚀

TypeScript library to create dynamic forms with automatic validation using Zod

ZodsForm allows you to create complete HTML forms from Zod schemas or JSON structures, with real-time validation, and a simple, intuitive API.

Form Preview

✨ Features

  • 🎯 Auto-generation from Zod: Convert Zod schemas into functional forms
  • Real-time validation: Validation while user types and on blur
  • 🔄 Nested field support: Objects and complex structures with dot-notation
  • Array support: Dynamic arrays with add/remove controls and element-level validation
  • �📦 Native TypeScript: Full typing and autocomplete
  • 🎭 Input masks: Automatic formatting (phone, credit card, ZIP, etc.) with string or regex
  • 🔐 Password fields: With automatic show/hide toggle
  • 🎨 Custom components: 3-level system (global, instance, field)
  • 🎛️ Dynamic properties: Change visibility, size, options in real-time
  • 🌐 Multiple input types: text, number, email, password, textarea, select, checkbox
  • 🎯 Auto-mounting: Specify the element where the form will be mounted
  • 🔍 Cross-field validation: Support for Zod's .refine() (e.g., confirm password)

📦 Installation

npm install zodsform zod

Or with yarn:

yarn add zodsform zod

Note: ZodsForm uses pure CSS (no CSS frameworks required). Just import the styles:

import 'zodsform/dist/styles.css'

Or in HTML:

<link rel="stylesheet" href="node_modules/zodsform/dist/styles.css">

No additional dependencies or build tools needed! The CSS file is ready to use out of the box.

🚀 Quick Start

1. From a Zod Schema with Extensions (Recommended)

import { ZodsForm, z } from "zodsform";
import "zodsform/dist/styles.css";

// Define your Zod schema with ZodsForm extensions
const userSchema = z.object({
  firstName: z.string()
    .min(2, "Minimum 2 characters")
    .label("First Name")                  // 🏷️ Custom label
    .properties({ size: 6 }),             // 📏 Grid size (1-12)
  
  lastName: z.string()
    .min(2, "Minimum 2 characters")
    .label("Last Name")
    .properties({ size: 6 }),
  
  email: z.string()
    .email("Invalid email")
    .label("Email Address"),
  
  age: z.number()
    .min(18, "Must be 18 or older")
    .label("Age")
    .properties({ visible: false }),      // 👁️ Hidden by default
});

// Create the form automatically with element selector
const form = ZodsForm.fromSchema(userSchema, {
  el: "#root", // CSS selector or DOM element where it will be mounted
  onSubmit: ({ data }) => {
    console.log("Validated data:", data);
    // Send to your API
  },
  onChange: ({ fieldPath, data, errors, arrayIndex }) => {
    // 🎯 fieldPath has strong typing based on schema!
    // Autocomplete: "email" | "password" | "age" | "security.password"
    // arrayIndex indicates which array element changed (if applicable)
    console.log("Field changed:", fieldPath);
    console.log("Array index:", arrayIndex); // undefined for non-array fields
    console.log("Current data:", data);
    console.log("Errors:", errors);
  },
  onValidate: ({ isValid, data, errors }) => {
    console.log("Form valid:", isValid);
  }
});

// Automatically mounted on the specified element
form.render();

// Or you can mount it manually without specifying 'el':
// document.getElementById("root")?.appendChild(form.render());

2. With Input Masks

const contactSchema = z.object({
  phone: z.string()
    .length(10, "Phone must have 10 digits")
    .label("Phone")
    .properties({ mask: "###-###-####" }),  // 📞 Format mask
  
  zipCode: z.string()
    .length(5, "Invalid ZIP code")
    .label("ZIP Code")
    .properties({ mask: "#####" }),
  
  creditCard: z.string()
    .length(16, "Invalid card")
    .label("Credit Card")
    .properties({ mask: "####-####-####-####" }),
  
  customMask: z.string()
    .label("Only numbers 1-6")
    .properties({ mask: /^[1-6]\d{0,5}$/ }),  // 🎭 Regex mask
});

3. With Password Fields

const securitySchema = z.object({
  security: z.object({
    password: z.string()
      .min(8, "Minimum 8 characters")
      .label("Password")
      .password(),  // 🔐 Automatic show/hide toggle
    
    confirmPassword: z.string()
      .label("Confirm Password")
      .password(),
  }).label("Security"),
})
.refine((data) => data.security.password === data.security.confirmPassword, {
  message: "Passwords do not match",
  path: ["security.confirmPassword"],  // ✅ Cross-field validation
});

📚 Zod Extensions

ZodsForm extends Zod with chainable methods to configure fields:

| Method | Description | Example | |--------|-------------|---------| | .label(string) | Defines the label text | z.string().label("Full Name") | | .properties({...}) | Configures field properties | z.string().properties({ size: 6, mask: "###-####" }) | | .password(boolean) | Converts to password field with toggle | z.string().password() | | .component(ComponentConfig) | Assigns a custom component | z.boolean().component(ToggleSwitch) |

Available Properties

.properties({
  size?: number;           // Grid size (1-12 columns)
  visible?: boolean;       // Show/hide field
  disabled?: boolean;      // Enable/disable
  placeholder?: string;    // Placeholder text
  mask?: string | RegExp;  // Input mask
  // For select:
  options?: Array<{
    value: string | number;
    label: string;
  }>;
})

📚 Supported Field Types

| Type | Description | Zod Example | |------|-------------|-------------| | text | Text input | z.string() | | number | Numeric input | z.number() | | email | Email input | z.string().email() | | password | Password input with toggle | z.string().password() | | textarea | Text area | z.string() (long) | | select | Dropdown selector | z.enum(["a", "b"]) or z.enum({ a: "A", b: "B" }) | | checkbox | Checkbox | z.boolean() | | array | Dynamic array with controls | z.array(z.object({...})) | | section | Visual container | - | | box | Bordered container | z.object({...}).label("Title") |

🔥 Advanced Examples

Form with Nested Objects

const addressSchema = z.object({
  user: z.object({
    name: z.string().label("Name"),
    email: z.string().email().label("Email"),
  }).label("User"),  // 📦 Box with title
  
  address: z.object({
    street: z.string().label("Street"),
    city: z.string().label("City"),
    zipCode: z.string()
      .length(5)
      .label("ZIP Code")
      .properties({ mask: "#####" }),
  }).label("Address"),  // 📦 Another box
});

const form = ZodsForm.fromSchema(addressSchema, {
  onSubmit: (data) => {
    console.log(data);
    // {
    //   user: { name: "...", email: "..." },
    //   address: { street: "...", city: "...", zipCode: "12345" }
    // }
  },
});

Form with Select (Enum)

// Simple enum (uses values as labels)
const simpleEnum = z.object({
  role: z.enum(["admin", "user", "guest"]).label("Role"),
});

// Enum with custom labels (recommended)
const customEnum = z.object({
  country: z.enum({
    mx: "Mexico",
    us: "United States",
    es: "Spain",
    ar: "Argentina"
  }).label("Country"),
});

const form = ZodsForm.fromSchema(customEnum);

Form with Custom Validations

const schema = z.object({
  username: z
    .string()
    .min(3, "Minimum 3 characters")
    .max(20, "Maximum 20 characters")
    .regex(/^[a-zA-Z0-9_]+$/, "Only letters, numbers and underscore"),
  
  age: z
    .number()
    .min(18, "Must be 18 or older")
    .max(100, "Invalid age"),
  
  website: z
    .string()
    .url("Invalid URL")
    .optional(),
});

Form with Arrays

const securitySchema = z.object({
  security: z.array(
    z.object({
      password: z.string()
        .min(8, "Minimum 8 characters")
        .label("Password")
        .password(),
      
      confirmPassword: z.string()
        .label("Confirm Password")
        .password(),
    })
  ).label("Security Credentials")
  .min(1, "At least one set of credentials is required"),
})
.refine((data) => {
  // Validate that passwords match in all array elements
  return data.security.every(item => item.password === item.confirmPassword);
}, {
  message: "Passwords do not match",
  path: ["security", 0, "confirmPassword"],  // Error shown on first element
});

const form = ZodsForm.fromSchema(securitySchema, {
  onChange: ({ fieldPath, data, arrayIndex }) => {
    // arrayIndex indicates which array element changed
    if (arrayIndex !== undefined) {
      console.log(`Field ${fieldPath} changed in array element ${arrayIndex}`);
    }
  },
  onSubmit: ({ data }) => {
    console.log(data);
    // {
    //   security: [
    //     { password: "...", confirmPassword: "..." },
    //     { password: "...", confirmPassword: "..." }
    //   ]
    // }
  }
});

Array Features:

  • Add button: "+ Agregar" button to add new elements
  • Remove button: Each element has a remove button
  • 🛡️ Smart deletion: If only one element remains, it clears the data instead of removing the element
  • Independent validation: Each array element validates independently
  • 🎯 Element tracking: arrayIndex parameter in onChange callback to identify which element changed
  • 🔍 Cross-field validation: refine() works correctly with array elements

🎛️ API

ZodsForm.fromSchema(schema, config)

Creates a form from a Zod schema.

Parameters:

  • schema: Zod schema (ZodObject)
  • config:
    • el?: CSS selector (string) or DOM element where to mount the form
    • onValidate?: Callback when the form is validated ({ isValid, data, errors }) => void
    • onSubmit?: Callback when the form is submitted ({ data }) => void
    • onChange?: Callback on each field change ({ fieldPath, data, errors, arrayIndex }) => void
      • fieldPath has strong typing based on schema structure (includes array element properties!)
      • arrayIndex? is a number indicating which array element changed (undefined for non-array fields)
    • components?: Custom components map

Returns: ZodsForm instance

Example:

const form = ZodsForm.fromSchema(mySchema, {
  el: "#app", // Automatically mounted on this element
  onSubmit: ({ data }) => console.log(data),
  onChange: ({ fieldPath, data, errors, arrayIndex }) => {
    // fieldPath has autocomplete: "email" | "password" | "nested.field" | "array.property"
    console.log('Changed:', fieldPath);
    if (arrayIndex !== undefined) {
      console.log('Array element index:', arrayIndex);
    }
  }
});
form.render();

new ZodsForm(config, el?)

Creates a form from a JSON structure.

Parameters:

  • config.structure: Form structure
  • config.schema?: Optional Zod schema for full validation
  • config.onSubmit?: Submit callback ({ data }) => void
  • config.onChange?: Change callback ({ fieldPath, data, errors, arrayIndex }) => void
  • config.onValidate?: Validate callback ({ isValid, data, errors }) => void
  • config.components?: Custom components map
  • el?: CSS selector or DOM element (optional)

Instance Methods

form.render(): HTMLElement

Renders the form and returns the DOM element.

form.getData(): any

Gets the current form data.

form.getErrors(): any

Gets the current validation errors.

form.setData(data: Record<string, any>): void

Sets values in the form programmatically.

form.setData({
  firstName: "John",
  email: "[email protected]",
  age: 25,
});

form.setFieldProperty(fieldPath, property, value): void

Dynamically changes any field property in the form in real-time. Supports nested fields with dot-notation.

// Hide/show fields
form.setFieldProperty("companyName", "visible", false);
form.setFieldProperty("address.street", "visible", true);  // ✅ Nested fields

// Change grid size (1-12 columns)
form.setFieldProperty("email", "size", 12);

// Enable/disable fields
form.setFieldProperty("zipCode", "disabled", true);

// Update select options
form.setFieldProperty("country", "options", [
  { value: "mx", label: "Mexico" },
  { value: "us", label: "USA" }
]);

// Change min/max limits
form.setFieldProperty("age", "min", 18);

// Apply custom CSS classes
form.setFieldProperty("email", "className", "bg-yellow-100");

// Change masks (string or regex)
form.setFieldProperty("phone", "mask", "###-###-####");

Supported properties: visible, size, disabled, className, min, max, options, mask

🎭 Mask System

ZodsForm supports two types of masks for data input:

1. Format Masks (String)

Apply automatic formatting while the user types. The # symbol represents a digit:

const maskedSchema = z.object({
  phone: z.string().properties({ mask: "###-###-####" }),      // 555-123-4567
  zipCode: z.string().properties({ mask: "#####" }),            // 12345
  creditCard: z.string().properties({ mask: "####-####-####-####" }), // 1234-5678-9012-3456
});

Important: The value is stored WITHOUT formatting (digits only), but displayed formatted in the input.

2. Validation Masks (RegExp)

Validate each character in real-time according to a regular expression:

const regexMaskSchema = z.object({
  customCode: z.string().properties({ 
    mask: /^[1-6]\d{0,5}$/  // Only numbers 1-6, maximum 6 digits
  }),
  alphanumeric: z.string().properties({
    mask: /^[a-zA-Z0-9]*$/  // Only letters and numbers
  }),
});

🔧 Grid System

ZodsForm uses a 12-column grid system based on CSS Grid. Use the size property to control width:

const gridSchema = z.object({
  firstName: z.string()
    .label("First Name")
    .properties({ size: 6 }),  // 50% width (6/12)
  
  lastName: z.string()
    .label("Last Name")
    .properties({ size: 6 }),  // 50% width (6/12)
  
  email: z.string()
    .label("Email")
    .properties({ size: 12 }), // 100% width (12/12)
});

Common sizes:

  • size: 3 → 25% (3/12)
  • size: 4 → 33% (4/12)
  • size: 6 → 50% (6/12)
  • size: 8 → 66% (8/12)
  • size: 12 → 100% (12/12) - default for boxes and textareas

🎨 Custom Components

ZodsForm allows you to completely customize the appearance of your forms with three levels of customization in priority order:

1. Field-level Components (Zod) - Highest Priority

const schema = z.object({
  notifications: z.boolean()
    .label("Notifications")
    .component(ToggleSwitch),  // 🎯 Only this field uses ToggleSwitch
});

2. Instance-level Components

const form = ZodsForm.fromSchema(schema, {
  components: {
    boolean: MyCustomToggle,      // By type
    acceptTerms: StyledCheckbox,  // By field name
  },
  onSubmit: (data) => console.log(data)
});

3. Global Components

import { ZodsForm } from "zodsform";

// In your main.ts or index.ts
ZodsForm.registerComponents({
  boolean: ToggleSwitch,  // All booleans use toggle
});

Creating Custom Components

import type { ComponentConfig } from "zodsform";

export const ToggleSwitch: ComponentConfig = {
  render: (config) => {
    const wrapper = document.createElement('div');
    const input = document.createElement('input');
    input.type = 'checkbox';
    input.name = config.fieldPath;
    // ... your custom UI logic
    wrapper.appendChild(input);
    return wrapper;
  },
  getValue: (element) => {
    return element.querySelector('input')?.checked || false;
  },
  setValue: (element, value) => {
    const input = element.querySelector('input');
    if (input) input.checked = Boolean(value);
  }
};

🤝 Contributing

Contributions are welcome! Please:

  1. Fork the project
  2. Create a feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

🔄 Migration from v0.1.x

If you were using previous versions, here are the main changes:

  • Nested fields fixed: Validation of fields within objects now works correctly
  • Improved masks: Support for regex in addition to string patterns
  • Improved components: Clearer 3-level priority system
  • ⚠️ Removed methods: labels in config no longer exists, use .label() in schema

❓ FAQ

Los estilos no se muestran después de instalar el paquete

Solución: Asegúrate de importar el CSS en tu proyecto:

import 'zodsform/dist/styles.css';

O en HTML:

<link rel="stylesheet" href="node_modules/zodsform/dist/styles.css">

El CSS está listo para usar sin necesidad de herramientas de build adicionales.

¿Necesito instalar algún framework CSS?

No. ZodsForm usa CSS puro sin dependencias externas. Todo está incluido en el archivo dist/styles.css. Solo necesitas instalar zodsform y zod.

¿Puedo personalizar los estilos?

Sí, de tres formas:

  1. CSS personalizado: Sobreescribe las clases de ZodsForm en tu propio CSS
  2. Componentes personalizados: Crea tus propios componentes con tu lógica de UI
  3. Propiedades inline: Usa className en .properties() para agregar clases personalizadas

� Additional Documentation

👤 Author

Dinnger

🙏 Acknowledgments

  • Zod - TypeScript-first validation library
  • The TypeScript community

📝 License

ISC License - see the LICENSE file for more details


Made with ❤️ by Dinnger