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

hookra

v1.0.11

Published

Build dynamic, type-safe React forms from JSON schemas. 17+ field types, conditional logic, nested structures, validation — zero boilerplate. Powered by React Hook Form + Chakra UI.

Readme

Hookra

JSON-driven form builder on top of React Hook Form + Chakra UI.
Better DX than RJSF. Type-safe. Tree-shakable. Zero config.

npm install hookra

Hookra uses Chakra UI v3 for its UI. If you don't already have it set up, install the peer dependencies:

npm install @chakra-ui/react @emotion/react react-hook-form

Getting started

Step 1 — Wrap your app with ChakraProvider

Hookra renders Chakra UI components, so your app needs a ChakraProvider at the root. If you already have one, skip this step.

// main.tsx (or index.tsx)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
import { App } from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ChakraProvider value={defaultSystem}>
      <App />
    </ChakraProvider>
  </StrictMode>,
)

Custom theme? Pass your own system instead of defaultSystem:

import { createSystem, defaultConfig } from '@chakra-ui/react'
const system = createSystem(defaultConfig, { theme: { /* overrides */ } })
<ChakraProvider value={system}>…</ChakraProvider>

Step 2 — Define a schema

// schema.ts
import type { FormSchema } from 'hookra'

export const contactSchema: FormSchema = {
  title: 'Contact Us',
  fields: [
    { name: 'name',    type: 'text',     label: 'Name',    required: true },
    { name: 'email',   type: 'email',    label: 'Email',   required: true },
    { name: 'message', type: 'textarea', label: 'Message', rows: 4 },
  ],
}

Step 3 — Render <FormBuilder>

// ContactForm.tsx
import { FormBuilder } from 'hookra'
import { contactSchema } from './schema'

export function ContactForm() {
  return (
    <FormBuilder
      schema={contactSchema}
      onSubmit={(data) => console.log(data)}
    />
  )
}

That's it. The form renders with validation, labels, and a submit button out of the box.

Field types

| type | Description | |------|-------------| | text email password url tel search | Text input variants | | number integer | Numeric input with stepper | | textarea | Multi-line text (optional char count) | | boolean switch | Toggle switch | | checkbox | Single checkbox | | select | Native dropdown | | multiselect | Multi-select via checkboxes | | radio | Radio button group | | checkboxgroup | Checkbox group (multi-value) | | date time datetime | Date/time pickers | | file | File upload (accept, maxSize) | | color | Color picker | | slider | Range slider | | hidden | Hidden field (value included in submit) | | array | Dynamic list — add/remove rows | | object | Nested group of fields | | custom | Your own component via registry |

Schema reference

FormSchema

{
  title?: string
  description?: string
  layout?: { columns?: number }   // grid columns (default 1)
  fields?: FieldSchema[]          // flat list
  sections?: FormSection[]        // grouped with titles
  submitLabel?: string
  showReset?: boolean
  resetLabel?: string
}

Common field properties

{
  name: string          // required — used as form value key
  type: FieldType       // required
  label?: string
  description?: string  // helper text
  placeholder?: string
  defaultValue?: unknown
  required?: boolean | string   // true or custom error message
  disabled?: boolean
  readOnly?: boolean
  hidden?: boolean      // excludes from render AND form values
  width?: 'full' | 'half' | 'third' | 'quarter' | 'two-thirds' | 'three-quarters' | 1–12
  validation?: FieldValidation
  dependsOn?: Condition // conditional visibility
  props?: Record<string, any>   // forwarded to Chakra component
}

Validation

validation: {
  required?: boolean | string
  min?: number | { value: number; message: string }
  max?: number | { value: number; message: string }
  minLength?: number | { value: number; message: string }
  maxLength?: number | { value: number; message: string }
  pattern?: string | { value: string; message: string }  // regex
  validate?: Record<string, (value) => boolean | string>
}

Conditions (dependsOn)

Simple — show when country equals "us":

{ "field": "country", "value": "us" }

With operator:

{ "field": "age", "operator": "gte", "value": 18 }

Compound — AND:

{ "all": [
  { "field": "country", "value": "us" },
  { "field": "role", "operator": "ne", "value": "guest" }
]}

Compound — OR:

{ "any": [
  { "field": "plan", "value": "pro" },
  { "field": "plan", "value": "enterprise" }
]}

Negation:

{ "not": { "field": "subscribed", "operator": "truthy" } }

All operators

| Operator | Meaning | |----------|---------| | eq | == (default) | | ne | != | | gt gte lt lte | Numeric comparisons | | in | Value is in array | | nin | Value is NOT in array | | contains | String contains | | startsWith endsWith | String start/end | | matches | Regex test | | empty notEmpty | null/undefined/''/"" | | truthy falsy | Boolean coercion |

Array fields

{
  name: 'phoneNumbers',
  type: 'array',
  label: 'Phone Numbers',
  minItems: 1,
  maxItems: 5,
  addLabel: 'Add phone',
  itemSchema: {
    type: 'object',
    name: 'phone',
    fields: [
      { name: 'type',   type: 'select', label: 'Type', options: [...] },
      { name: 'number', type: 'tel',    label: 'Number' },
    ],
  },
}

Object (nested) fields

{
  name: 'address',
  type: 'object',
  label: 'Address',
  collapsible: true,
  fields: [
    { name: 'street', type: 'text', label: 'Street', width: 'full' },
    { name: 'city',   type: 'text', label: 'City' },
    { name: 'zip',    type: 'text', label: 'Zip' },
  ],
}

Sections

sections: [
  {
    title: 'Personal Info',
    description: 'Your basic details',
    columns: 2,
    collapsible: true,
    dependsOn: { field: 'type', value: 'individual' },
    fields: [...],
  },
]

Multi-column layout

Set layout.columns on the form, then override per-field with width:

{
  layout: { columns: 3 },
  fields: [
    { name: 'a', type: 'text', label: 'A' },          // 1 col
    { name: 'b', type: 'text', label: 'B', width: 'full' },   // all 3 cols
    { name: 'c', type: 'text', label: 'C', width: 'two-thirds' }, // 2 cols
  ],
}

<FormBuilder> props

<FormBuilder
  schema={schema}                 // required
  onSubmit={(data) => {}}         // required
  onCancel={() => {}}             // optional
  defaultValues={{ name: 'Joe' }} // override schema defaults
  registry={{ myField: MyComp }}  // custom field components
  readOnly={false}
  loading={false}
  mode="onBlur"                   // RHF validation mode
  submitButton={<Button>Save</Button>}  // custom submit
  cancelButton={null}             // hide cancel
/>

Accessing RHF methods (ref)

import { useRef } from 'react'
import { FormBuilder, type FormBuilderRef } from 'hookra'

const ref = useRef<FormBuilderRef>(null)

<FormBuilder ref={ref} schema={schema} onSubmit={handleSubmit} />

// Programmatic submit / reset
ref.current?.submit()
ref.current?.reset()
ref.current?.form.setValue('email', '[email protected]')
ref.current?.form.watch('country')

Custom field components

import { FormBuilder, createRegistry, defaultRegistry } from 'hookra'

function StarRating({ field, name }) {
  const { control } = useFormContext()
  // ... your implementation
}

<FormBuilder
  schema={schema}
  registry={{ starRating: StarRating }}
  onSubmit={handleSubmit}
/>

In the schema:

{ name: 'rating', type: 'custom', component: 'starRating', label: 'Rating' }

Tree-shaking

The package is marked "sideEffects": false. If you only use a subset of field types the unused field modules are eliminated by your bundler automatically.

You can also use individual field components directly:

import { TextField, SelectField } from 'hookra'

Peer dependencies

react >= 18
react-dom >= 18
react-hook-form >= 7
@chakra-ui/react >= 3
@emotion/react >= 11

All of the above must be installed in your project. See Getting started for the install command.

Dev / Demo

npm install
npm run dev        # Vite dev server with full demo on http://localhost:5173
npm run build      # Build library → dist/
npm run typecheck  # TypeScript check with no emit

License

MIT