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

@saastro/forms

v0.1.3

Published

Sistema de formularios dinámicos para React + Zod + React Hook Form

Readme

@saastro/forms

Dynamic form system for React with Zod validation, React Hook Form, and a plugin system.

Features

  • 30+ field types (text, select, date, slider, OTP, button-card, etc.)
  • Zero-config mode: Just pass your shadcn components inline
  • Multi-step forms with conditional navigation
  • Zod schema validation per field
  • Plugin system (localStorage, analytics, autosave)
  • Modular submit actions (HTTP, webhook, email)
  • Dependency injection for UI components
  • Builder pattern API
  • Responsive 12-column grid layout
  • TypeScript strict mode

Installation

npm install @saastro/forms
# or
bun add @saastro/forms

Peer Dependencies

npm install react react-dom react-hook-form zod date-fns react-day-picker

shadcn/ui Components

Install only the components you need. For a basic text form:

npx shadcn@latest add input button label field form

For all available field types:

npx shadcn@latest add input button label textarea select checkbox radio-group popover calendar tooltip slider switch separator dialog command input-otp field form

Tailwind CSS 4

If you're using Tailwind CSS 4, add the following @source directive to your main CSS file so Tailwind can detect the classes used by this package:

@source "node_modules/@saastro/forms/dist/**/*.js";

Quick Start (Zero-Config with Auto-Discovery)

The simplest way to use @saastro/forms — auto-discover all your shadcn components with Vite's glob:

import { Form, FormBuilder } from '@saastro/forms';

// Auto-discover all shadcn components at build time
const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });

const config = FormBuilder.create('contact')
  .addField('name', (f) => f.type('text').label('Name').required().minLength(2))
  .addField('email', (f) => f.type('email').label('Email').required().email())
  .addStep('main', ['name', 'email'])
  .buttons({ submit: { type: 'submit', label: 'Send' } })
  .build();

export function ContactForm() {
  return (
    <Form
      config={config}
      components={uiComponents}
      onSubmit={(values) => console.log('Submitted:', values)}
    />
  );
}

That's it! No FormProvider, no barrel file, no manual component registration. The Form auto-discovers components by parsing the glob result.

Alternative: Explicit Components

If you prefer explicit imports or need to override specific components:

import { Form, FormBuilder } from '@saastro/forms';
import { Input, Button, Label } from '@/components/ui';
import { Field, FieldLabel, FieldDescription, FieldError } from '@/components/ui/field';
import { FormField, FormControl } from '@/components/ui/form';

<Form
  config={config}
  components={{
    Input,
    Button,
    Label,
    Field,
    FieldLabel,
    FieldDescription,
    FieldError,
    FormField,
    FormControl,
  }}
/>;

Benefits of Zero-Config

  1. One line of setup — just import.meta.glob and you're done
  2. Auto-discovers components — no manual registration needed
  3. Missing component fallback — shows helpful warnings with install commands
  4. No Provider boilerplate — just render <Form />
  5. Override per-form — mix glob with explicit overrides

Quick Start (Provider Mode)

For apps with many forms, you can still use the provider pattern:

import { Form, FormBuilder, ComponentProvider, createComponentRegistry } from '@saastro/forms';
import * as shadcn from '@/lib/form-components'; // Your barrel file

const registry = createComponentRegistry(shadcn);

export function App() {
  return (
    <ComponentProvider components={registry}>
      <ContactForm />
      <SignupForm />
      <CheckoutForm />
    </ComponentProvider>
  );
}

function ContactForm() {
  const config = FormBuilder.create('contact')
    .addField('name', (f) => f.type('text').label('Name').schema(z.string().min(2)))
    .addStep('main', ['name'])
    .build();

  return <Form config={config} />;
}

Which Components Do I Need?

Each field type requires certain components. When a component is missing, you'll see a helpful warning with install instructions.

| Field Type | Required Components | | ---------------------- | ------------------------------------------------------------------------- | | text, email, tel | Input, Label, Field, FieldLabel, FieldError, FormField, FormControl | | textarea | Textarea, Label, Field, FieldLabel, FieldError, FormField, FormControl | | select | Select, SelectTrigger, SelectContent, SelectItem, SelectValue, Field, ... | | checkbox, switch | Checkbox/Switch, Label, Field, ... | | date | Calendar, Popover, PopoverTrigger, PopoverContent, Button, Field, ... | | combobox | Command, CommandInput, CommandList, Popover, Button, Field, ... |

Use getRequiredComponents(config) to programmatically check:

import { getRequiredComponents, getInstallCommand } from '@saastro/forms';

const required = getRequiredComponents(config);
// ['Input', 'Button', 'Label', 'Field', ...]

const installCmd = getInstallCommand(required);
// 'npx shadcn@latest add input button label field form'

Multi-Step Form

const config = FormBuilder.create('signup')
  .layout('manual')
  .columns(2)
  .addField('email', (f) => f.type('email').label('Email').schema(z.string().email()))
  .addField('password', (f) => f.type('text').label('Password').schema(z.string().min(8)))
  .addField('name', (f) => f.type('text').label('Full Name').schema(z.string().min(2)))
  .addField('plan', (f) =>
    f
      .type('button-radio')
      .label('Choose Plan')
      .schema(z.string())
      .options([
        { label: 'Free', value: 'free' },
        { label: 'Pro', value: 'pro' },
      ]),
  )
  .addStep('account', ['email', 'password'])
  .addStep('profile', ['name', 'plan'])
  .initialStep('account')
  .buttons({
    submit: { type: 'submit', label: 'Create Account' },
    next: { type: 'next', label: 'Next' },
    back: { type: 'back', label: 'Back' },
  })
  .build();

Plugins

import { FormBuilder, PluginManager, localStoragePlugin, analyticsPlugin } from '@saastro/forms';

const plugins = new PluginManager();
plugins.register(localStoragePlugin({ key: 'my-form' }));
plugins.register(analyticsPlugin());

const config = FormBuilder.create('my-form')
  .usePlugins(plugins)
  // ... fields and steps
  .build();

Submit Actions

const config = FormBuilder.create('form')
  // ... fields and steps
  .build();

// Add submit actions to config
config.submitActions = {
  sendToAPI: {
    id: 'sendToAPI',
    action: {
      name: 'Send to API',
      type: 'http',
      config: {
        url: 'https://api.example.com/submit',
        method: 'POST',
      },
    },
    trigger: { type: 'onSubmit' },
    order: 1,
  },
};

Conditional Fields

// Hide field based on another field's value
.addField("company", (f) =>
  f.type("text")
    .label("Company")
    .schema(z.string().optional())
    .hidden((values) => values.type !== "business")
)

// Responsive visibility
.addField("sidebar", (f) =>
  f.type("html")
    .hidden({ default: "hidden", lg: "visible" })
)

// Declarative conditions
.addField("discount", (f) =>
  f.type("text")
    .label("Discount Code")
    .schema(z.string().optional())
    .hidden({
      operator: "and",
      conditions: [
        { field: "plan", operator: "equals", value: "free" },
      ],
    })
)

Component Override

Override specific components per-form, even when using a provider:

import { MyCustomDatePicker } from './components/MyCustomDatePicker';

<Form
  config={config}
  components={{
    Calendar: MyCustomDatePicker, // Override just the Calendar
  }}
/>;

Field Types

| Type | Description | | ---------------------- | ------------------------------------- | | text, email, tel | Text inputs | | textarea | Multi-line text | | select | Dropdown select | | native-select | Native HTML select | | combobox | Searchable select (Popover + Command) | | command | Command palette select | | checkbox, switch | Boolean toggles | | radio | Radio group | | button-radio | Button-style radio | | button-checkbox | Button-style multi-select | | button-card | Card-style selection | | checkbox-group | Multiple checkboxes | | switch-group | Multiple switches | | date | Date picker (simple or popover) | | daterange | Date range picker | | slider | Range slider | | otp | OTP input | | input-group | Input with prefix/suffix | | html | Raw HTML content |

API Reference

Form Props

interface FormProps {
  /** Form configuration */
  config: FormConfig;
  /** Component overrides (zero-config mode) */
  components?: PartialComponentRegistry;
  /** Called on successful submit */
  onSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
  /** Called on submit error */
  onError?: (error: Error) => void;
  /** CSS class for form element */
  className?: string;
}

Utility Functions

// Get components needed for a form
getRequiredComponents(config: FormConfig): ComponentName[]

// Get missing components
getMissingComponents(required: ComponentName[], provided: Partial<...>): ComponentName[]

// Generate install command
getInstallCommand(missing: ComponentName[]): string

License

MIT