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

@waysnx/ui-form-builder

v0.3.0

Published

Schema-driven form builder from WaysNX - renders forms from JSON Schema using ui-core components

Readme

@waysnx/ui-form-builder

Schema-driven form builder from WaysNX. Renders dynamic forms from JSON Schema using @waysnx/ui-core components.

Installation

npm install @waysnx/ui-form-builder @waysnx/ui-core react-datepicker

Note: Don't forget to import CSS files:

import '@waysnx/ui-core/dist/index.css';
import '@waysnx/ui-form-builder/dist/index.css';
import 'react-datepicker/dist/react-datepicker.css';

Choosing the Right Package

This package provides schema-driven forms. For simpler installation:

  • Recommended: Install @waysnx/ui-kit (includes ui-core + ui-form-builder)
  • Modular: Install @waysnx/ui-core + @waysnx/ui-form-builder (this approach)

📖 Installation Guide for detailed comparison.

Features

  • DynamicForm - Complete form from JSON schema + layout — zero manual code
  • SchemaRenderer - Convert JSON Schema to form fields
  • DynamicField - Resolve a single schema property to a ui-core component
  • FormArray - Dynamic repeatable form sections (add/remove rows)
  • Conditional Logic - Show/hide, enable/disable, and require fields based on conditions
  • Secure by Design - No XSS, SSRF, or code injection vulnerabilities

Usage

DynamicForm — Full Form from JSON

The simplest way to render a form. Provide a JSON schema and a layout, and DynamicForm handles everything — field rendering, layout, state, validation, and buttons.

import { DynamicForm } from '@waysnx/ui-form-builder';
import '@waysnx/ui-core/dist/index.css';
import '@waysnx/ui-form-builder/dist/index.css';

const schema = {
  type: 'object',
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    email: { type: 'string', format: 'email' },
  },
  required: ['firstName', 'email'],
};

const layout = {
  rows: [
    {
      cells: [
        { settings: { fieldName: 'firstName', title: 'First Name', controlType: 'input', inputType: 'text', 'x-col-size': 6, required: true } },
        { settings: { fieldName: 'lastName', title: 'Last Name', controlType: 'input', inputType: 'text', 'x-col-size': 6 } },
        { settings: { fieldName: 'email', title: 'Email', controlType: 'input', inputType: 'email', 'x-col-size': 12, required: true } },
      ],
      settings: { rowId: 'row-1' },
    },
  ],
  settings: {
    fieldGroup: 'Contact Info',
    buttonsPosition: 'bottom',
    buttonsAlignment: 'text-right',
    buttons: [
      { label: 'Cancel', name: 'cancel', type: 'button', appearance: 'accent' },
      { label: 'Save', name: 'save', type: 'submit', appearance: 'primary' },
    ],
  },
};

function App() {
  return (
    <DynamicForm
      schema={schema}
      formLayout={layout}
      onSubmit={(data) => console.log(data)}
      onBtnClick={(name) => console.log('Button:', name)}
    />
  );
}

DynamicForm Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | schema | JSONSchema \| string | required | JSON Schema (object or JSON string) | | formLayout | FormLayout \| string | required | Layout config (object or JSON string) | | formData | Record<string, any> | - | Initial form values | | formClass | string | '' | CSS class for the form element | | isFormReadonly | boolean | false | Make all fields read-only | | onSubmit | (data) => void | - | Called on form submit | | onBtnClick | (buttonName) => void | - | Called on non-submit button click | | onFieldChange | (name, value, formData) => void | - | Called on any field change | | onFormReady | (form) => void | - | Exposes getData() and reset() |

Layout Cell Settings

Each cell in the layout can override schema properties:

| Setting | Type | Description | |---------|------|-------------| | fieldName | string | Maps to schema property name | | title | string | Label override | | controlType | string | Control type: input, select, textarea, hidden, toggle, checkbox, radio, slider, file-upload, autocomplete, html-editor, etc. | | inputType | string | For input controls: text, number, email, tel, password | | x-col-size | number | Column width (1–12 grid) | | required | boolean | Override required state | | readonly | boolean | Make field read-only | | disabled | boolean | Disable field | | pattern | string | Validation pattern | | x-columns | number | Number of columns for Radio/Checkbox grid layout | | x-searchable | boolean | Enable search box in Select dropdowns | | x-rows | number | Number of rows for Textarea and SpeechToTextTextarea |

Layout Settings

| Setting | Type | Description | |---------|------|-------------| | fieldGroup | string | Wraps form in fieldset with legend | | infoText | string | Info text shown above fields | | buttonsPosition | 'top' \| 'bottom' | Button placement | | buttonsAlignment | 'text-left' \| 'text-right' \| 'text-center' | Button alignment | | buttons | LayoutButton[] | Array of button configs | | path | string | API endpoint (metadata) | | method | string | HTTP method (metadata) |

Schema to Form Fields (Manual Layout)

import { schemaToFormFields } from '@waysnx/ui-form-builder';
import { useState } from 'react';

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string', title: 'Full Name' },
    email: { type: 'string', format: 'email', title: 'Email' },
  },
  required: ['name', 'email'],
};

const fields = schemaToFormFields(schema, formData, (name, value) => {
  setFormData(prev => ({ ...prev, [name]: value }));
});

// Render fields
{fields.map((field) => (
  <div key={field.name}>
    {field.useFormFieldLabel && (
      <label>
        {field.label}
        {field.required && <span>*</span>}
      </label>
    )}
    {field.component}
  </div>
))}

FormArray - Dynamic Repeatable Sections

Add/remove multiple instances of a form group dynamically.

import { FormArray } from '@waysnx/ui-form-builder';

<FormArray
  label="Bank Accounts"
  itemSchema={{
    type: 'object',
    properties: {
      bank_name: { type: 'string', title: 'Bank Name' },
      account_no: { type: 'string', title: 'Account Number' },
      ifsc_code: { type: 'string', title: 'IFSC Code' },
    },
    required: ['bank_name', 'account_no'],
  }}
  value={bankAccounts}
  onChange={setBankAccounts}
  addButtonTitle="Add Bank Account"
  deleteButtonTitle="Remove"
  minItems={1}
  maxItems={5}
/>

FormArray Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | Display label for the array section | | itemSchema | JSONSchema | required | JSON Schema for each item | | value | Record<string, any>[] | [] | Current array value | | onChange | (value) => void | - | Called when array changes | | canAdd | boolean | true | Show add button | | canDelete | boolean | true | Show delete buttons | | addButtonTitle | string | 'Add' | Custom add button text | | deleteButtonTitle | string | 'Remove' | Custom delete button text | | minItems | number | 0 | Minimum number of items | | maxItems | number | - | Maximum number of items | | disabled | boolean | false | Read-only mode |

FormArray with Conditional Logic

Fields within FormArray support conditional logic:

<FormArray
  label="Employees"
  itemSchema={{
    type: 'object',
    properties: {
      employmentType: {
        type: 'string',
        title: 'Type',
        enum: ['fulltime', 'contractor'],
      },
      ssn: {
        type: 'string',
        title: 'SSN',
        'x-show-when': [{ name: 'employmentType', value: 'fulltime' }],
      },
      taxId: {
        type: 'string',
        title: 'Tax ID',
        'x-show-when': [{ name: 'employmentType', value: 'contractor' }],
      },
    },
  }}
  value={employees}
  onChange={setEmployees}
/>

Nested FormArray (Child Forms in Parent Forms)

FormArray supports unlimited nesting depth - you can have arrays within arrays, creating parent-child form relationships. This is useful for complex data structures like companies with employees, or projects with tasks and subtasks.

Pattern: Use type: 'array' with items.type: 'object' to create nested child forms.

<FormArray
  label="Companies"
  itemSchema={{
    type: 'object',
    properties: {
      company_name: { type: 'string', title: 'Company Name' },
      // Nested array of employees (child form)
      employees: {
        type: 'array',
        title: 'Employees',
        items: {
          type: 'object',  // ← This creates a nested child form
          properties: {
            name: { type: 'string', title: 'Employee Name' },
            email: { type: 'string', format: 'email', title: 'Email' },
            position: { type: 'string', title: 'Position' },
          },
        },
        'x-add-button-title': 'Add Employee',
        'x-min-items': 1,
      },
    },
  }}
  value={companies}
  onChange={setCompanies}
/>

How it works:

  • Parent form has a single Company Name field
  • Each company has a nested Employees array (child form)
  • Each employee can have their own nested arrays (e.g., addresses)
  • Supports unlimited nesting depth
  • Each nesting level gets its own add/remove buttons
  • Conditional logic works at all nesting levels

Example data structure:

[
  {
    "company_name": "Acme Corp",
    "employees": [
      {
        "name": "John Doe",
        "email": "[email protected]",
        "position": "Developer"
      },
      {
        "name": "Jane Smith",
        "email": "[email protected]",
        "position": "Designer"
      }
    ]
  }
]

Conditional Logic

Control field visibility, disabled state, and required validation based on other field values.

x-show-when - Conditional Visibility

Show/hide fields based on conditions:

const schema = {
  type: 'object',
  properties: {
    accountType: {
      type: 'string',
      title: 'Account Type',
      enum: ['personal', 'business'],
    },
    companyName: {
      type: 'string',
      title: 'Company Name',
      'x-show-when': [{ name: 'accountType', value: 'business' }],
    },
  },
};

x-disable-when - Conditional Disabled State

Enable/disable fields based on conditions:

const schema = {
  type: 'object',
  properties: {
    hasShippingAddress: {
      type: 'boolean',
      title: 'Different shipping address',
    },
    shippingStreet: {
      type: 'string',
      title: 'Shipping Street',
      'x-disable-when': [{ name: 'hasShippingAddress', value: false }],
    },
  },
};

x-required-when - Conditional Required Validation

Make fields required based on conditions:

const schema = {
  type: 'object',
  properties: {
    contactMethod: {
      type: 'string',
      title: 'Contact Method',
      enum: ['email', 'phone', 'both'],
    },
    email: {
      type: 'string',
      format: 'email',
      title: 'Email',
      'x-required-when': [
        { name: 'contactMethod', value: 'email' },
        { name: 'contactMethod', value: 'both' },
      ],
    },
    phone: {
      type: 'string',
      title: 'Phone',
      'x-required-when': [
        { name: 'contactMethod', value: 'phone' },
        { name: 'contactMethod', value: 'both' },
      ],
    },
  },
};

Supported Operators

| Operator | Description | Example | |----------|-------------|---------| | == (default) | Equal to | { name: 'age', value: 18 } | | != | Not equal to | { name: 'status', value: 'inactive', operator: '!=' } | | > | Greater than | { name: 'age', value: 18, operator: '>' } | | < | Less than | { name: 'age', value: 65, operator: '<' } | | >= | Greater than or equal | { name: 'age', value: 18, operator: '>=' } | | <= | Less than or equal | { name: 'age', value: 65, operator: '<=' } | | notEmpty | Field has value | { name: 'comments', operator: 'notEmpty' } | | isEmpty | Field is empty | { name: 'comments', operator: 'isEmpty' } |

Complex Conditions Example

const schema = {
  type: 'object',
  properties: {
    age: {
      type: 'integer',
      title: 'Age',
    },
    parentConsent: {
      type: 'boolean',
      title: 'Parent Consent Required',
      'x-show-when': [{ name: 'age', value: 18, operator: '<' }],
    },
    seniorDiscount: {
      type: 'boolean',
      title: 'Senior Discount',
      'x-show-when': [{ name: 'age', value: 65, operator: '>=' }],
    },
    comments: {
      type: 'string',
      title: 'Comments',
    },
    reviewComments: {
      type: 'string',
      title: 'Review Comments',
      'x-show-when': [{ name: 'comments', operator: 'notEmpty' }],
    },
  },
};

Theming

All form components use CSS variables from @waysnx/ui-core for theming. Override these in your CSS:

:root {
  --wx-color-primary: #f19924;       /* Buttons, focus rings */
  --wx-color-text: #1e293b;          /* Labels, text */
  --wx-color-text-muted: #64748b;    /* Hints, descriptions */
  --wx-color-error: #ef4444;         /* Required asterisks, errors */
  --wx-color-border: #e2e8f0;        /* Fieldset borders */
  --wx-color-surface-alt: #f8fafc;   /* Fieldset backgrounds */
}

These are the same variables used across all WaysNX libraries. See the Theming Guide for the full list.

Security

This library is designed with security as a top priority:

  • No XSS vulnerabilities - All HTML is sanitized via DOMPurify in @waysnx/ui-core
  • No SSRF vulnerabilities - Applications control all HTTP requests
  • No Code Injection - Uses safe operator-based conditional logic (no eval() or new Function())

For detailed security information, contact [email protected]

TypeScript Support

Full TypeScript support with exported types:

import type { 
  JSONSchema, 
  JSONSchemaProperty, 
  FormFieldConfig,
  ControlCondition,
  FormArrayProps,
  FormLayout,
  GridRow,
  GridCell,
  CellSettings,
  RowSettings,
  LayoutSettings,
  LayoutButton,
  DynamicFormProps,
} from '@waysnx/ui-form-builder';

Exported Functions

import {
  DynamicForm,
  schemaToFormFields,
  FormArray,
  resolveField,
  evaluateCondition,
  evaluateConditions,
  shouldShowField,
  shouldDisableField,
  shouldRequireField,
} from '@waysnx/ui-form-builder';

Peer Dependencies

  • react >= 18
  • @waysnx/ui-core >= 0.1.0

Links

License

MIT