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

hazo_data_forms

v2.0.3

Published

Dynamic form rendering from JSON schema with document link support and PDF viewer integration

Readme

hazo_data_forms

Dynamic form rendering from JSON schema with document link support and embedded PDF viewer integration.

Overview

hazo_data_forms is a React library that transforms JSON schema definitions into fully functional forms with rich field types, dual-mode rendering (edit/view), and integrated PDF document viewing capabilities. Build complex data collection interfaces without writing form components.

Key Features

  • 17 Field Types: text, number, date, boolean, option, email, tel, currency, percentage, textarea, table, computed, masked, static_text, summary_row, abn, tfn
  • Dual Mode Rendering: Switch between edit mode (editable) and view mode (read-only display)
  • Document Links: Click inline doc links to open PDFs in resizable side panel
  • Schema-Driven: Define forms declaratively in JSON
  • Config-Based Styling: INI configuration file for consistent styling across projects
  • Computed Fields: Automatic calculation based on other field values
  • Table Fields: Dynamic arrays with configurable columns
  • Built-in Validation: Required fields, min/max, length constraints, custom validators
  • TypeScript: Full type safety and IntelliSense support
  • Extensible: Register custom field renderers without forking

Installation

npm install hazo_data_forms react react-dom react-hook-form react-icons

# Required: For file management (upload/delete/storage)
npm install hazo_files

# Optional: For PDF viewer support
npm install hazo_pdf

# Optional: For server-side config loading
npm install hazo_config

Important: This library requires Tailwind CSS to be configured in your project. See the Quick Start section for setup instructions.

Peer Dependencies

  • React: ^18.0.0 or ^19.0.0
  • react-hook-form: ^7.0.0
  • hazo_files: ^1.4.0 (required - file management service)
  • hazo_config: ^1.0.0 (optional)
  • hazo_pdf: ^1.6.0 (optional - for PDF viewer)

Quick Start

1. Configure Tailwind CSS

This library uses Tailwind CSS classes with semantic CSS variables compatible with shadcn/ui theming.

Tailwind v4: The library uses semantic color classes (bg-muted, text-primary, border-destructive, etc.) that work with Tailwind v4. If you experience missing utility class styles, add the package to your CSS source paths:

/* app/globals.css */
@import "tailwindcss";

/* Include hazo_data_forms for Tailwind to scan utility classes */
@source "../node_modules/hazo_data_forms/dist";

Tailwind v3: Add the library's source to your Tailwind config:

// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/hazo_data_forms/dist/**/*.{js,mjs,cjs}",
  ],
  // ... rest of your config
};

Note: This library expects shadcn/ui CSS variables to be defined (e.g., --background, --foreground, --primary, --muted, --destructive, etc.). If you're using shadcn/ui, these are already configured. Otherwise, see the shadcn/ui theming documentation for the required CSS variables.

2. Import the component

import { HazoDataForm, FormSchema } from "hazo_data_forms";

3. Define your form schema

const schema: FormSchema = [
  {
    section_name: "Personal Information",
    sub_sections: [
      {
        sub_section_id: "basic_info",
        sub_section_label: "Basic Details",
        field_group: {
          orientation: "vertical",
          fields: [
            {
              id: "first_name",
              label: "First Name",
              field_info: {
                field_type: "text",
                required: true,
                placeholder: "Enter your first name"
              }
            },
            {
              id: "email",
              label: "Email Address",
              field_info: {
                field_type: "email",
                required: true
              }
            },
            {
              id: "birth_date",
              label: "Date of Birth",
              field_info: {
                field_type: "date"
              }
            }
          ]
        }
      }
    ]
  }
];

4. Render the form

function MyForm() {
  const handle_submit = (values: FormValues) => {
    console.log("Form submitted:", values);
  };

  return (
    <HazoDataForm
      schema={schema}
      mode="edit"
      on_submit={handle_submit}
    />
  );
}

Field Types

Text Fields

// Basic text
{
  id: "username",
  label: "Username",
  field_info: {
    field_type: "text",
    required: true,
    min_length: 3,
    max_length: 20
  }
}

// Email
{
  id: "email",
  label: "Email",
  field_info: { field_type: "email" }
}

// Phone
{
  id: "phone",
  label: "Phone Number",
  field_info: { field_type: "tel" }
}

// Textarea
{
  id: "comments",
  label: "Comments",
  field_info: {
    field_type: "textarea",
    rows: 4,
    max_length: 500
  }
}

Numeric Fields

// Number
{
  id: "age",
  label: "Age",
  field_info: {
    field_type: "number",
    min: 0,
    max: 120
  }
}

// Currency
{
  id: "salary",
  label: "Annual Salary",
  field_info: {
    field_type: "currency",
    currency_symbol: "$",
    decimal_places: 2
  }
}

// Percentage
{
  id: "discount",
  label: "Discount Rate",
  field_info: {
    field_type: "percentage",
    decimal_places: 1,
    min: 0,
    max: 100
  }
}

Selection Fields

// Boolean (checkbox)
{
  id: "agree_terms",
  label: "I agree to the terms and conditions",
  field_info: {
    field_type: "boolean",
    required: true
  }
}

// Option (dropdown)
{
  id: "country",
  label: "Country",
  field_info: {
    field_type: "option",
    options: [
      { label: "United States", value: "US" },
      { label: "Canada", value: "CA" },
      { label: "United Kingdom", value: "UK" }
    ],
    required: true
  }
}

Date Field

{
  id: "start_date",
  label: "Start Date",
  field_info: {
    field_type: "date",
    required: true
  }
}

Computed Field

{
  id: "total",
  label: "Total Amount",
  field_info: {
    field_type: "computed",
    computed_formula: "price * quantity * (1 - discount / 100)",
    computed_dependencies: ["price", "quantity", "discount"]
  }
}

Table Field (Arrays)

{
  id: "line_items",
  label: "Line Items",
  field_info: {
    field_type: "table",
    table_columns: [
      {
        id: "description",
        label: "Description",
        field_info: { field_type: "text" },
        width: "40%"
      },
      {
        id: "quantity",
        label: "Qty",
        field_info: { field_type: "number", min: 1 },
        width: "20%"
      },
      {
        id: "unit_price",
        label: "Unit Price",
        field_info: { field_type: "currency" },
        width: "20%"
      },
      {
        id: "total",
        label: "Total",
        field_info: {
          field_type: "computed",
          computed_formula: "quantity * unit_price"
        },
        width: "20%"
      }
    ],
    table_min_rows: 1,
    table_max_rows: 50
  }
}

Reference Values

Add reference annotations below fields to show prior-year values, benchmarks, or expected values:

{
  id: "annual_revenue",
  label: "Annual Revenue",
  field_info: { field_type: "currency", decimal_places: 2 },
  reference_value: "$1,250,000.00 (FY2024)"
}

The reference_value displays below the field input as italic, smaller text with a grey background. It appears in both edit and view modes. Fields without reference_value render normally with no extra space.

Supported on all field types including table columns.

Reference Values in Tables

Table columns support reference_value for a static annotation on every row. For per-row/per-cell annotations, include _reference_values in your row data:

// Column-level (same reference for every row)
{
  id: "amount",
  label: "Amount",
  field_info: { field_type: "currency" },
  reference_value: "$10,000 (benchmark)"
}

// Row-level (different reference per row)
const table_data = [
  {
    description: "Office Rent",
    amount: 14000,
    _reference_values: { amount: "$12,500.00 (FY2024)" }
  },
  {
    description: "Travel",
    amount: 5600,
    _reference_values: { amount: "$4,800.00 (FY2024)" }
  }
];

Row-level _reference_values take priority over column-level reference_value.

Document Links

Add inline document links that open PDFs in an embedded viewer:

{
  id: "contract_value",
  label: "Contract Value",
  field_info: { field_type: "currency" },
  doc_links: [
    {
      type: "pdf",
      url: "/documents/contract.pdf",
      page: 3  // Optional: Open to specific page
    }
  ]
}

PDF Panel Configuration

<HazoDataForm
  schema={schema}
  show_pdf_panel={true}
  pdf_panel_position="right"  // "right" | "left" | "bottom"
  pdf_panel_width="500px"
  pdf_panel_resizable={true}
/>

Services

hazo_data_forms uses a service injection pattern for external integrations. Services can be passed via the services prop or via HazoServicesProvider.

File Manager Service

File upload, deletion, and PDF save operations are handled through the file_manager service from hazo_files.

import { HazoDataForm } from "hazo_data_forms";
import { createFileManager } from "hazo_files";

// Server-side: use hazo_files FileManager directly
const file_manager = createFileManager({ configPath: "./hazo_files.ini" });
await file_manager.initialize();

// Client-side: create an adapter that calls API routes
// (see test-app/lib/client_file_manager.ts for an example)

<HazoDataForm
  schema={schema}
  services={{ file_manager }}
  enable_file_upload={true}
  file_save_path="/clients/acme/tax_2024/"
  pdf_save_path="/clients/acme/tax_2024/pdfs/"
/>

File upload is enabled when all three conditions are met:

  • enable_file_upload={true} prop is set
  • config.file_upload.enabled is true (from INI config)
  • file_manager.isInitialized() returns true

Using the Services Provider

Wrap your app or form area with HazoServicesProvider to provide services via context:

import { HazoServicesProvider } from "hazo_data_forms";

<HazoServicesProvider services={{ file_manager, logger, db }}>
  <HazoDataForm schema={schema} enable_file_upload={true} />
</HazoServicesProvider>

Accessing Services in Custom Components

import { useHazoFileManager, useHazoLogger, useHazoDb } from "hazo_data_forms";

function MyCustomComponent() {
  const file_manager = useHazoFileManager();
  const logger = useHazoLogger();
  const db = useHazoDb();
}

Form Modes

Edit Mode (Editable)

<HazoDataForm
  schema={schema}
  mode="edit"
  on_submit={(values) => {
    // Handle form submission
    console.log(values);
  }}
/>

View Mode (Read-Only)

const existing_data = {
  first_name: "John",
  email: "[email protected]",
  birth_date: "1990-05-15"
};

<HazoDataForm
  schema={schema}
  mode="view"
  values={existing_data}
/>

Configuration

Using INI Configuration File

Create a configuration file (e.g., /public/config/hazo_data_forms_config.ini):

[colors]
label_color = #374151
field_border_color = #d1d5db
field_border_color_focus = #3b82f6

[fonts]
label_font_size = 14px
field_font_size = 14px

[spacing]
section_spacing = 32px
field_spacing = 16px

[formatting]
default_currency_symbol = $
date_format = MMM d, yyyy

Pass the path to your component:

<HazoDataForm
  schema={schema}
  config_path="/config/hazo_data_forms_config.ini"
/>

Runtime Configuration Override

<HazoDataForm
  schema={schema}
  config_override={{
    label_color: "#1e40af",
    field_border_color: "#60a5fa",
    default_currency_symbol: "€",
    date_format: "dd/MM/yyyy"
  }}
/>

API Reference

HazoDataForm Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | schema | FormSchema | required | Form schema defining sections and fields | | mode | "edit" \| "view" | "edit" | Form mode (edit allows input, view is read-only) | | values | FormValues | {} | Controlled form values | | default_values | FormValues | {} | Default values for uncontrolled form | | on_change | (values: FormValues) => void | - | Callback when any field value changes | | on_field_change | (field_id: string, value: unknown) => void | - | Callback when specific field changes | | on_submit | (values: FormValues) => void | - | Callback when form is submitted | | on_doc_link_click | (doc_link: DocLink) => void | - | Callback when document link is clicked | | show_pdf_panel | boolean | true | Whether to show PDF panel for doc links | | pdf_panel_position | "left" \| "right" | "right" | Position of PDF panel | | pdf_panel_width | string | from config | Width of PDF panel | | pdf_panel_resizable | boolean | true | Whether PDF panel is resizable | | config_path | string | - | Path to INI configuration file | | config_override | PartialFormConfig | - | Runtime configuration overrides | | errors | FormErrors | - | External validation errors | | validate_on_blur | boolean | true | Validate fields on blur | | validate_on_change | boolean | false | Validate fields on change | | validate | (values: FormValues) => FormErrors | - | Custom validation function | | class_name | string | - | Additional CSS class for form container | | show_section_headers | boolean | true | Show section headers | | show_sub_section_headers | boolean | true | Show sub-section headers | | collapsible_sections | boolean | false | Make sections collapsible | | collapsed_sections | string[] | [] | IDs of initially collapsed sections | | on_form_ready | (methods: UseFormReturn) => void | - | Callback with react-hook-form methods | | show_submit_button | boolean | - | Show submit button at bottom of form | | submit_button_text | string | "Submit" | Text for submit button | | services | HazoServices | - | Service instances (file_manager, logger, db) | | enable_file_upload | boolean | false | Enable file upload UI | | file_save_path | string | - | Base storage path for uploaded files | | pdf_save_path | string | - | Separate path for PDF saves (falls back to file_save_path) |

Custom Field Renderers

Register custom field types:

import { register_field_renderer, type FieldRendererProps } from "hazo_data_forms";

function CustomField({ field, value, on_change, mode, config }: FieldRendererProps) {
  // Your custom field implementation
  return <div>...</div>;
}

register_field_renderer("custom_type", CustomField);

Then use in your schema:

{
  id: "my_field",
  label: "My Custom Field",
  field_info: {
    field_type: "custom_type" as any
  }
}

Exported Types

import type {
  // Schema types
  FormSchema,
  FormSection,
  SubSection,
  FieldGroup,
  FormField,
  FieldInfo,
  FieldType,
  OptionItem,
  TableColumn,

  // Runtime types
  FormValues,
  FormErrors,
  FormMode,
  DocLink,
  DocLinkClickEvent,
  PdfPanelPosition,

  // Configuration
  FormConfig,
  PartialFormConfig,

  // Services
  HazoServices,
  HazoFilesFileManager,  // Re-exported from hazo_files
  HazoConnectInstance,
  Logger,

  // Component props
  HazoDataFormProps,
  FieldRendererProps,
  FieldRenderer,
} from "hazo_data_forms";

Exported Utilities

import {
  // Styling
  cn,

  // Formatting
  format_currency,
  format_percentage,
  format_date,

  // Parsing
  parse_boolean,
  parse_number,
  parse_string,

  // Computed fields
  evaluate_formula,

  // Utilities
  generate_id,
  deep_merge,
  sanitize_filename,

  // Service hooks
  useHazoFileManager,
  useHazoLogger,
  useHazoDb,
  useHazoCustomService,

  // Service provider
  HazoServicesProvider,
} from "hazo_data_forms";

Styling

This library uses Tailwind CSS with semantic CSS variables for styling, ensuring compatibility with both Tailwind v3 and v4. The library uses shadcn/ui-compatible color tokens:

  • Background colors: bg-background, bg-muted, bg-primary/10, bg-destructive/10
  • Text colors: text-foreground, text-muted-foreground, text-primary, text-destructive
  • Border colors: border-border, border-input, border-primary, border-destructive

All CSS classes use the cls_ prefix following hazo ecosystem conventions:

  • cls_hazo_data_form - Main form container
  • cls_section - Section container
  • cls_sub_section - Sub-section container
  • cls_field_group - Field group container
  • cls_field_wrapper - Individual field wrapper
  • cls_field_label - Field label
  • cls_field_input - Field input element
  • cls_doc_link_button - Document link button
  • cls_pdf_panel - PDF panel container

You can override these styles in your own CSS or use the configuration system to customize colors, fonts, and spacing.

Examples

Complete Form Example

See the test-app directory in the repository for a complete Next.js example with various field types and PDF integration.

View Mode

<HazoDataForm
  schema={schema}
  mode="view"
  values={savedData}
/>

Validation

<HazoDataForm
  schema={schema}
  validate={(values) => {
    const errors: FormErrors = {};
    if (!values.email?.includes("@")) {
      errors.email = "Invalid email address";
    }
    return errors;
  }}
  validate_on_blur={true}
/>

Accessing Form Methods

import React from "react";
import type { UseFormReturn } from "react-hook-form";
import { HazoDataForm } from "hazo_data_forms";

function MyForm() {
  const formMethodsRef = React.useRef<UseFormReturn | null>(null);

  return (
    <HazoDataForm
      schema={schema}
      on_form_ready={(methods) => {
        formMethodsRef.current = methods;
      }}
    />
  );
}

Migrating to v2.0.0

v2.0.0 replaces file operation callbacks with a file_manager service. This is a breaking change.

Removed Props

  • on_file_upload - replaced by file_manager service
  • on_file_delete - replaced by file_manager service
  • on_pdf_save - replaced by file_manager service

Migration Steps

Before (v1.x):

<HazoDataForm
  schema={schema}
  enable_file_upload={true}
  on_file_upload={async (request) => {
    const result = await my_api.upload(request.file);
    return { success: true, uploaded_file: result };
  }}
  on_file_delete={async (field_id, file_id) => {
    await my_api.delete(file_id);
    return true;
  }}
  on_pdf_save={(pdf_bytes, filename, url) => {
    my_api.save_pdf(pdf_bytes, filename);
  }}
/>

After (v2.0.0):

const file_manager: HazoFilesFileManager = {
  uploadFile: async (data, path) => {
    const result = await my_api.upload(data, path);
    return { success: true, data: { id: result.id, name: result.name, path, size: result.size, mimeType: result.type } };
  },
  deleteFile: async (path) => {
    await my_api.delete(path);
    return { success: true };
  },
  downloadFile: async (path) => ({ success: true, data: await my_api.download(path) }),
  isInitialized: () => true,
  exists: async (path) => my_api.exists(path),
  ensureDirectory: async (path) => { await my_api.mkdir(path); return { success: true }; },
};

<HazoDataForm
  schema={schema}
  services={{ file_manager }}
  enable_file_upload={true}
  file_save_path="/uploads/my_form/"
/>

Migrating to v2.1.0

v2.1.0 replaces the local HazoFileManagerInstance interface with the FileManager class imported directly from hazo_files. This is a breaking change.

What Changed

  • hazo_files is now a required peer dependency (was optional)
  • HazoFileManagerInstance is removed — use HazoFilesFileManager (re-exported from hazo_files)
  • HazoServices.file_manager is typed as required — the runtime still handles missing services gracefully

Migration Steps

  1. Install hazo_files:
npm install hazo_files
  1. Update type imports:
// Before
import type { HazoFileManagerInstance } from "hazo_data_forms";

// After
import type { HazoFilesFileManager } from "hazo_data_forms";
// Or import directly:
import type { FileManager } from "hazo_files";
  1. If using a client-side adapter (plain object), add a type assertion:
// Client adapters that implement a subset of FileManager methods
// need a type assertion since FileManager is a class
return { uploadFile, deleteFile, ... } as unknown as HazoFilesFileManager;

License

MIT

Repository

https://github.com/pub12/hazo_data_forms