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

@insticc/genericform

v2.1.3

Published

A generic form with a comprehensive set of field types, validation, and external state management

Readme

@insticc/genericform

A flexible, schema-driven React form component that renders any combination of field types from a plain JavaScript array. No boilerplate, built-in validation, modal support, and full external state control out of the box.


Table of Contents


Installation

npm install @insticc/genericform

Import the component and its stylesheet:

import GenericForm from '@insticc/genericform';

Peer Dependencies

The following must be installed in your project:

| Package | Version | |---|---| | react | ≥ 17 | | react-dom | ≥ 17 | | react-bootstrap | ≥ 2 | | semantic-ui-react | ≥ 2 | | react-datetime | ≥ 3 |


Quick Start

import { useState } from 'react';
import GenericForm from '@insticc/genericform';
import '@insticc/genericform/dist/index.css';

const MyForm = () => {
  const [result, setResult] = useState(null);

  const fields = [
    { name: 'firstName', type: 'text',   label: 'First Name', required: true,  value: '' },
    { name: 'lastName',  type: 'text',   label: 'Last Name',  required: true,  value: '' },
    { name: 'email',     type: 'email',  label: 'Email',      required: true,  value: '' },
    { name: 'age',       type: 'int',    label: 'Age',        min: 0, max: 120, value: '' },
    {
      name: 'country', type: 'select', label: 'Country', value: '',
      options: [
        { value: 'PT', text: 'Portugal' },
        { value: 'ES', text: 'Spain' },
        { value: 'FR', text: 'France' },
      ],
    },
  ];

  return (
    <GenericForm
      title="New User"
      fields={fields}
      submitText="Save"
      onSubmit={(data) => setResult(data)}
      onCancel={() => setResult(null)}
    />
  );
};

The onSubmit callback receives the complete form data object keyed by each field's name.


Field Types

| type value | Rendered as | Key extra props | |---|---|---| | text / string | Text input | placeholder, minLength, maxLength | | email | Email input (format validated) | placeholder | | password | Password input | placeholder | | int / number | Number input | min, max, step | | textArea | Textarea | rows, height, maxLength | | date | Date picker (DD/MM/YYYY) | placeholder | | datetime | Date + time picker | placeholder | | time | Time picker (HH:mm) | placeholder | | year | Year picker | minYear, maxYear | | select | Native <select> | options, placeholder, multiple | | multipleDropdown | Semantic UI multi-select | options, placeholder | | search | Semantic UI Search | options, isLoading, resultRenderer | | toggle | Toggle switch | — | | checkbox | Single checkbox | onOptionChange | | checkboxGroup | Group of checkboxes | options, grouped, onOptionChange | | radioGroup | Stacked radio buttons | options, onOptionChange | | radioGroupInline | Inline radio buttons | options, onOptionChange | | json | JSON textarea editor | datatype, allowModeSwitch, storeAsString, rows | | accordion | Collapsible section | sections, defaultOpen, activeAllIndex | | component | Arbitrary JSX embed | component | | label | Section heading | — | | hr / separator | Horizontal rule | — |

Options format

The select, multipleDropdown, checkboxGroup, and radio types all accept options in any of these shapes — the component resolves them automatically:

{ value: 'pt', text: 'Portugal' }   // most common
{ id: 'admin', label: 'Admin' }
{ name: 'read', label: 'Read' }

Field Schema Reference

Every item in the fields array is a plain object. The properties below apply across all (or most) field types.

Common properties

| Property | Type | Default | Description | |---|---|---|---| | name | string | — | Required. Unique key; used as the form data key. | | type | string | 'text' | Field type (see table above). | | label / displayName | string \| node | — | Label shown above the field. | | value | any | — | Initial (or controlled) value. | | required | bool | false | Adds * indicator and enforces non-empty on submit. | | disabled | bool | false | Disables the input. | | placeholder | string | displayName | Input placeholder text. | | tooltip | string | — | Tooltip shown next to the label. | | showHelp | bool | false | Show ⓘ help icon. | | helpText | string | — | Text shown in the help icon tooltip. | | errorMessage | string | auto | Custom message shown on validation failure. | | validate | function | — | (value, formData) => string \| null. Return a string to show as error. | | onChange | function | — | (value, formData) => void. Per-field change callback (in addition to onFieldChange). | | hide | bool \| function | false | (formData, field) => bool. Hides the field dynamically. | | inline | bool | false | Renders this field side-by-side with adjacent inline fields. | | group | string | — | Groups fields under a named section heading. Groups render in alphabetical order, ungrouped fields last. |

Style properties (all optional)

| Property | Type | Description | |---|---|---| | style / mainDivStyle | object | Style applied to the outer wrapper <div>. | | labelStyle | object | Style applied to the <label> element. | | spanStyle | object | Style applied to the required * span. | | inputStyle | object | Style applied to the input element. | | tooltipStyle | object | Style applied to the tooltip span. | | className | string | Extra CSS class for text/email/password inputs. |

Type-specific properties

int / number

{ name: 'score', type: 'number', min: 0, max: 100, step: 0.5, value: '' }

textArea

{ name: 'bio', type: 'textArea', rows: 4, height: '120px', maxLength: 300, value: '' }

select

{
  name: 'country', type: 'select',
  options: [{ value: 'PT', text: 'Portugal' }],
  multiple: false,   // set true for native multi-select
  value: '',
}

checkboxGroup

{
  name: 'perms', type: 'checkboxGroup',
  value: ['read'],   // array of selected names
  options: [
    { name: 'read',   label: 'Read' },
    { name: 'write',  label: 'Write' },
    { name: 'delete', label: 'Delete' },
  ],
  grouped: false,   // true renders a visual group border
}

json

{
  name: 'config', type: 'json',
  datatype: 'object',       // 'string' | 'number' | 'object'
  allowModeSwitch: true,    // toggle between single / multiple (array) mode
  storeAsString: false,     // keep raw string in formData instead of parsed value
  rows: 6,
  value: '',
}

accordion

{
  name: 'advanced', type: 'accordion',
  defaultOpen: false,
  activeAllIndex: false,   // open all sections by default
  sections: [
    { title: 'Config',      icon: '⚙️', content: <p>…</p> },
    { title: 'Danger zone', icon: '⚠️', content: <p>…</p> },
  ],
}

component

{
  name: 'avatar', type: 'component',
  displayName: 'Avatar preview',
  component: <MyAvatarWidget />,
}

GenericForm Props

| Prop | Type | Default | Description | |---|---|---|---| | fields | array | [] | Required. Array of field schema objects. | | title | string \| node | — | Form heading. | | titleIcon | node | — | Icon rendered before the title. | | titleStyle | object | — | Style for the title element. | | onSubmit | async function | — | (formData) => void. Called after successful validation. | | handleSubmit | function | — | Legacy: receives the native submit event instead of formData. | | onCancel | function | — | Called when the Cancel button is clicked. | | onDataChange | function | — | (formData) => void. Fired on every field change with the full snapshot. | | onFieldChange | function | — | ({ name, value, option? }) => void. Fired per field change. | | validateOnChange | bool | true | Validate each field as the user types. | | validateOnSubmit | bool | true | Validate all fields before calling onSubmit. | | submitText | string | 'Submit' | Label on the submit button. | | cancelText | string | 'Cancel' | Label on the cancel button. | | showSubmit | bool | true | Show the submit button. | | showCancel | bool | true | Show the cancel button. | | haveSaveButton | bool | true | Alias for showSubmit. | | submitButton | object | {} | { className, style, icon } overrides for the submit button. | | cancelButton | object | {} | { className, style, icon } overrides for the cancel button. | | customActions | array | [] | Extra buttons in the actions bar (see below). | | splitForm | bool | false | Render fields in a two-column layout. | | splitField | string | — | Field name at which to split left/right columns. | | splitGroup | bool | false | Split at the group boundary instead of a specific field. | | enableModal | bool | false | Wrap the form in a React Bootstrap <Modal>. | | modalShow | bool | false | Controls modal visibility. | | modalSize | string | 'lg' | Bootstrap modal size ('sm', 'lg', 'xl'). | | disableDefaultContainer | bool | false | Remove the default card/shadow wrapper <div>. | | containerStyle | object | {} | Style for the outermost container. | | formStyle | object | — | Style for the <form> element. | | fieldsStyle | object | — | Style for each fields section wrapper. | | actionsDivStyle | object | — | Style for the actions bar wrapper. | | actionsStyle | object | — | Style applied to every action button. | | handleSearchChange | function | — | Passed directly to the search field type. | | handleResultSelect | function | — | Passed directly to the search field type. | | resultRenderer | function | — | Custom result item renderer for the search type. |

customActions

Inject additional buttons into the actions bar:

<GenericForm
  fields={fields}
  customActions={[
    {
      label: 'Reset',
      icon: <ResetIcon />,        // optional
      className: 'ufg-button-secondary',
      style: {},
      disabled: false,
      onClick: () => resetMyForm(),
    },
  ]}
/>

Validation

Validation runs on change (if validateOnChange) and on submit (if validateOnSubmit). Built-in rules cover the most common cases:

| Rule | Prop | Example | |---|---|---| | Required | required: true | Non-empty value | | Email format | type: 'email' | Must match [email protected] | | Min length | minLength: 3 | At least 3 characters | | Max length | maxLength: 20 | At most 20 characters | | Numeric min | min: 0 | Value ≥ 0 | | Numeric max | max: 100 | Value ≤ 100 | | Custom | validate function | Any logic |

Custom validation:

{
  name: 'website', type: 'text', value: '',
  validate: (value, formData) => {
    if (value && !value.startsWith('https://')) return 'Must start with https://';
    return null; // no error
  },
}

Cross-field validation — the second argument is the live form data object:

{
  name: 'notifyEmail', type: 'email', value: '',
  hide: (fd) => !fd.notify,
  validate: (value, fd) =>
    fd.notify && !value ? 'Required when notifications are enabled' : null,
}

Override the default error message with errorMessage:

{ name: 'name', type: 'text', required: true, errorMessage: 'Please enter your full name.' }

Layout & Grouping

Inline fields

Set inline: true on consecutive fields to render them side-by-side:

{ name: 'firstName', type: 'text', label: 'First name', inline: true, value: '' },
{ name: 'lastName',  type: 'text', label: 'Last name',  inline: true, value: '' },

Groups

Assign a group string to organise fields under labelled sections. Groups are sorted alphabetically; ungrouped fields appear last.

{ name: 'email',   type: 'email', group: 'Contact', value: '' },
{ name: 'phone',   type: 'text',  group: 'Contact', value: '' },
{ name: 'country', type: 'select', group: 'Address', value: '', options: [...] },

Two-column split

<GenericForm
  fields={fields}
  splitForm
  splitField="address"   // everything up to and including 'address' goes left
/>

Use splitGroup to split at a group boundary rather than a specific field.


External State Control

GenericForm manages its own internal state, but every field's value prop is watched. When a value changes, the form merges only that field — which means you can drive specific fields from a parent useState without re-initialising the whole form.

The recommended pattern for full external control uses onDataChange:

const [formState, setFormState] = useState({
  firstName: '', lastName: '', country: '', city: '',
});

// Auto-fill city when country changes
const handleFieldChange = ({ name, value }) => {
  if (name === 'country') {
    setFormState(prev => ({ ...prev, city: CITY_BY_COUNTRY[value] ?? '' }));
  }
};

const fields = [
  { name: 'firstName', type: 'text',   label: 'First name', value: formState.firstName },
  { name: 'lastName',  type: 'text',   label: 'Last name',  value: formState.lastName  },
  { name: 'country',   type: 'select', label: 'Country',    value: formState.country, options: COUNTRIES },
  { name: 'city',      type: 'text',   label: 'City',       value: formState.city, disabled: true },
];

<GenericForm
  fields={fields}
  onDataChange={setFormState}
  onFieldChange={handleFieldChange}
  onSubmit={(data) => console.log('Saved:', data)}
/>

You can also write directly to formState from outside the form (e.g. a "Load profile" button) — the form will detect the changed value props and update only the affected fields.


Modal Mode

Wrap the form in a React Bootstrap modal with no extra markup:

const [show, setShow] = useState(false);

<GenericForm
  title="Edit User"
  fields={fields}
  enableModal
  modalShow={show}
  modalSize="lg"
  onSubmit={(data) => { saveUser(data); setShow(false); }}
  onCancel={() => setShow(false)}
/>

Note: Import React Bootstrap's CSS in your app root if you haven't already:

import 'bootstrap/dist/css/bootstrap.min.css';

Theming (CSS Variables)

Override any of the following CSS custom properties to match your design system:

:root {
  --ufg-primary:        #1d4ed8;   /* submit button, active toggle */
  --ufg-primary-dark:   #1e40af;
  --ufg-primary-darker: #1e3a8a;
  --ufg-error:          #dc2626;   /* validation error text */
  --ufg-border:         #cbd5e1;   /* input borders */
  --ufg-bg:             #ffffff;   /* card background */
  --ufg-surface:        #fafafa;   /* secondary surfaces */
  --ufg-text:           #111111;   /* primary text */
  --ufg-text-secondary: #6b7280;   /* placeholder, help text */
  --ufg-radius:         10px;      /* input border radius */
  --ufg-shadow:         0 10px 30px rgba(0, 0, 0, 0.12);
}

Demo Components

Two reference implementations are bundled and exported:

import GenericForm, { Demo, Demo2 } from '@insticc/genericform';

// Every field type, conditional visibility, validation, custom actions:
<Demo />

// Full external state management with live formState sidebar:
<Demo2 />

Demo covers all 18 field types, conditional field visibility via hide, cross-field validation, inline layout, accordion, and custom action buttons.

Demo2 demonstrates the onDataChange + onFieldChange pattern for parent-driven state, including dependent field auto-fill (country → city) and a live field-change event log.


License

ISC