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

@yashjunagade/react-dynamic-forms

v1.0.0

Published

Render complex dynamic forms from a JSON schema. Supports 15+ question types, conditional logic, nested groups, and pluggable validation.

Readme

@yashjunagade/react-dynamic-forms

Render complex, fully-typed dynamic forms from a JSON schema.
Supports 12+ question types, AND/OR conditional logic, nested groups, pluggable validation, CSS variable theming, and a custom question plugin system — with zero runtime dependencies beyond React.


Install

npm install @yashjunagade/react-dynamic-forms

Peer dependencies (already in your project):

npm install react react-dom

Optional — Zod integration for schema validation:

npm install zod

Quick Start

import { FormRenderer } from "@yashjunagade/react-dynamic-forms";
import "@yashjunagade/react-dynamic-forms/dist/index.css";

const schema = {
  id: "contact",
  title: "Contact Us",
  questions: [
    { key: "name",    type: "textbox",  label: "Full Name",     validation: { required: true } },
    { key: "email",   type: "textbox",  label: "Email",         validation: { required: true, pattern: "^[^@]+@[^@]+\\.[^@]+$" } },
    { key: "message", type: "textarea", label: "Message",       validation: { required: true, minLength: 10 } },
  ],
};

export default function App() {
  return (
    <FormRenderer
      schema={schema}
      onSubmit={(values) => console.log(values)}
    />
  );
}

Question Types

| Type | Description | |---|---| | textbox | Single-line text input. Supports prefix, suffix. | | textarea | Multi-line text. Supports minRows, maxRows. | | number | Numeric input. Supports min, max, step, prefix, suffix. | | checkbox | Multi-select checkboxes. Supports layout: vertical / horizontal / grid. | | radio | Single-select radio buttons. Supports layout: vertical / horizontal. | | combo_select | Native <select> dropdown. Supports searchable, clearable. | | list_select | Scrollable checkbox list with optional search filter. | | multi_string | Multiple labeled text inputs that produce a Record<string, string>. | | file_upload | File drop zone. Supports accept, maxSizeMB, multiple. | | datetime | Date / time / datetime picker. Supports mode, minDate, maxDate. | | array | Repeatable text items. Supports minItems, maxItems, itemLabel. | | group | Nested container for other questions. Supports collapsible. | | config_only | Display-only HTML content (no input). | | custom | Your own component via the plugin system. |


Schema Reference

FormSchema

{
  id: string;
  title?: string;
  description?: string;
  questions: AnyQuestion[];
  submitLabel?: string;   // default: "Submit"
  resetLabel?: string;    // default: "Reset"
}

BaseQuestion (on every question type)

{
  key: string;
  type: QuestionType;
  label: string;
  description?: string;
  placeholder?: string;
  defaultValue?: unknown;
  disabled?: boolean;
  hidden?: boolean;
  validation?: ValidationRules;
  condition?: Condition;
  conditionBehavior?: "hide" | "disable";   // default: "hide"
  customStyles?: QuestionStyles;
  uiConfig?: QuestionUIConfig;
}

Validation

validation: {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
  minSelected?: number;   // for checkbox / list_select
  maxSelected?: number;
  minItems?: number;      // for array
  maxItems?: number;
  pattern?: string;       // regex string
  custom?: CustomValidator | CustomValidator[];
}

// CustomValidator signature
type CustomValidator = (value: unknown, allValues: Record<string, unknown>) => string | null;

Example — cross-field validation

{
  key: "confirm_password",
  type: "textbox",
  label: "Confirm Password",
  validation: {
    required: true,
    custom: (val, all) =>
      val !== all.password ? "Passwords do not match" : null,
  },
}

Conditional Logic

Single condition

{
  key: "reason",
  type: "textarea",
  label: "Reason for cancellation",
  condition: { when: "action", operator: "equals", value: "cancel" },
}

AND condition — all must pass

condition: {
  and: [
    { when: "role",   operator: "equals",    value: "admin" },
    { when: "active", operator: "not_equals", value: false   },
  ],
}

OR condition — any can pass

condition: {
  or: [
    { when: "plan", operator: "equals", value: "pro" },
    { when: "plan", operator: "equals", value: "enterprise" },
  ],
}

Available operators

| Operator | Works on | |---|---| | equals | any | | not_equals | any | | contains | string | | not_contains | string | | greater_than | number | | less_than | number | | is_empty | string / array | | is_not_empty | string / array | | includes | array | | not_includes | array |

Disable instead of hide

conditionBehavior: "disable"  // question stays visible but is disabled

Theming

Pass a theme prop to customise colours, radius, and font — no CSS overrides needed.

<FormRenderer
  schema={schema}
  theme={{
    primaryColor: "#8b5cf6",
    errorColor: "#dc2626",
    borderRadius: "10px",
    fontFamily: "Inter, sans-serif",
    fontSize: "15px",
  }}
/>

All values are applied as CSS variables on the <form> element, so you can also override them in CSS:

.my-form {
  --rdf-primary: #8b5cf6;
  --rdf-radius: 10px;
}

Custom Styles

Every question accepts a customStyles object to target individual DOM elements:

{
  key: "email",
  type: "textbox",
  label: "Email",
  customStyles: {
    container:    { marginBottom: 24 },
    label:        { fontWeight: 700, color: "#1e293b" },
    input:        { borderRadius: 10, fontSize: 15 },
    description:  { fontStyle: "italic" },
    error:        { color: "#dc2626", fontSize: 12 },
    inputWrapper: { gap: 8 },
    prefix:       { color: "#64748b" },
    suffix:       { color: "#64748b" },
    // For checkbox / radio / list_select:
    options: {
      option:       { padding: "6px 12px", borderRadius: 8 },
      optionLabel:  { fontWeight: 500 },
      optionInput:  { accentColor: "#6366f1" },
    },
    // For combo_select:
    dropdown: {
      wrapper: {},
      select:  { borderRadius: 10 },
    },
    // For array questions:
    addButton:    { background: "#6366f1", color: "#fff" },
    removeButton: { color: "#ef4444" },
  },
}

UI Config

Per-question behaviour that goes beyond styling:

uiConfig: {
  // Warning banner above the input
  warning: {
    showAlertIcon: true,
    message: "This will notify all admins.",
    subMessage: "Changes take effect immediately.",
  },

  // textbox: click-to-edit mode
  textBox: {
    showEditIcon: true,  // renders as text, ✏ to edit
  },

  // combo_select extras
  dropdown: {
    showClear: true,                           // show ✕ clear button
    noOptionText: "No options available",      // empty state message
  },

  // array question button labels
  array: {
    addLabel:    "Add email address",
    removeLabel: "Remove this email",
  },
}

Custom Question Types

Register your own components via the customQuestions prop:

import { FormRenderer } from "@yashjunagade/react-dynamic-forms";
import type { QuestionComponentProps } from "@yashjunagade/react-dynamic-forms";

function StarRating({ value, onChange }: QuestionComponentProps) {
  return (
    <div>
      {[1, 2, 3, 4, 5].map((n) => (
        <button key={n} type="button" onClick={() => onChange(n)}>
          {n <= (value as number) ? "★" : "☆"}
        </button>
      ))}
    </div>
  );
}

const schema = {
  id: "feedback",
  questions: [
    { key: "rating", type: "custom", customType: "star_rating", label: "Rate us" },
  ],
};

<FormRenderer
  schema={schema}
  customQuestions={{ star_rating: StarRating }}
/>

FormRenderer Props

| Prop | Type | Default | Description | |---|---|---|---| | schema | FormSchema | required | The form schema | | onSubmit | (values) => void | — | Called with flat values when form is valid | | onChange | (key, value, allValues) => void | — | Called on every field change | | theme | FormTheme | {} | CSS variable overrides | | readOnly | boolean | false | Disables all inputs, hides submit | | customQuestions | QuestionRegistry | {} | Custom question type components | | submitLabel | string | "Submit" | Submit button label | | resetLabel | string | "Reset" | Reset button label | | showReset | boolean | false | Show the reset button | | className | string | — | Extra class on the <form> element |


Advanced Hooks

For headless / fully custom rendering:

import {
  useFormState,
  useConditionalLogic,
  useValidation,
  useFormContext,
} from "@yashjunagade/react-dynamic-forms";
  • useFormState(schema) — reducer-based values/touched/showErrors state
  • useConditionalLogic(questions, values) — returns { visibleKeys, disabledKeys }
  • useValidation(questions, values, visibleKeys) — returns { errors, isValid }
  • useFormContext() — access context from inside a custom question component

License

MIT © Yash Junagade