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

@cermuel/smart-form

v1.0.0

Published

A type-safe, flexible React form component with built-in validation and Tailwind styling

Readme

SmartForm

A type-safe, flexible React form component with built-in validation and beautiful Tailwind styling. Build forms quickly with automatic validation, error handling, and customizable styling.

Features

Type-Safe - Full TypeScript support with intelligent type inference
🎨 Styled with Tailwind - Beautiful, modern design out of the box
Built-in Validation - Required fields, min/max values, regex patterns
🎯 Flexible - Use default UI or build completely custom forms
📦 Zero Config - Just import and use
🔒 Password Toggle - Built-in show/hide functionality
📝 Multiple Field Types - Text, email, number, password, textarea, checkbox, select, date, time
🎭 Custom Rendering - Full control over form layout and styling

Installation

npm install smart-form

Quick Start

1. Import the CSS (Required!)

In your main entry file (App.tsx, main.tsx, or _app.tsx):

import 'smart-form/dist/styles.css';

2. Use the Form

import { SmartForm } from 'smart-form';

function MyForm() {
  return (
    <SmartForm
      schema={{
        email: {
          type: 'email',
          label: 'Email Address',
          required: true,
        },
        password: {
          type: 'password',
          label: 'Password',
          required: true,
          min: 8,
        },
      }}
      onSubmit={(values) => {
        console.log(values); // { email: "...", password: "..." }
      }}
      buttonTitle="Sign In"
    />
  );
}

Field Types

Text Input

email: {
  type: 'email', // or 'string', 'number'
  label: 'Email',
  required: true,
  pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  icon: <MailIcon />,
}

Password

password: {
  type: 'password',
  label: 'Password',
  required: true,
  min: 8,
  passwordToggler: (visible) => visible ? <EyeOffIcon /> : <EyeIcon />,
}

Textarea

message: {
  type: 'textarea',
  label: 'Message',
  required: true,
  max: 500,
}

Checkbox

terms: {
  type: 'checkbox',
  label: 'Terms',
  checkboxLabel: 'I agree to the terms and conditions',
  required: true,
}

Select

country: {
  type: 'select',
  label: 'Country',
  required: true,
  placeholder: 'Select a country',
  options: [
    { value: 'us', label: 'United States', icon: <FlagIcon /> },
    { value: 'uk', label: 'United Kingdom' },
    { value: 'ca', label: 'Canada' },
  ],
}

Date & Time

birthdate: {
  type: 'date',
  label: 'Date of Birth',
  required: true,
}

appointmentTime: {
  type: 'time',
  label: 'Appointment Time',
  required: true,
}

Validation

Built-in Validators

schema={{
  age: {
    type: 'number',
    label: 'Age',
    required: true,
    min: 18,
    max: 100,
  },
  website: {
    type: 'string',
    label: 'Website',
    pattern: /^https?:\/\/.+/,
  },
}}

Validation Rules

  • required: boolean - Field must have a value
  • min: number - Minimum value for numbers
  • max: number - Maximum value for numbers
  • pattern: RegExp - Custom regex validation

Default Values

schema={{
  username: {
    type: 'string',
    label: 'Username',
    defaultValue: 'johndoe',
  },
  newsletter: {
    type: 'checkbox',
    checkboxLabel: 'Subscribe to newsletter',
    defaultValue: true,
  },
}}

Styling

Using Default Styles

<SmartForm
  schema={schema}
  onSubmit={handleSubmit}
  formClassname="max-w-md mx-auto"
  className="mb-4"
  labelClassname="font-bold"
  inputClassname="shadow-sm"
  buttonClassname="hover:bg-gray-800"
/>

Custom Button

<SmartForm
  schema={schema}
  onSubmit={handleSubmit}
  buttonTitle="Create Account"
  buttonLoading={isLoading}
  buttonDisabled={isDisabled}
  loadingIndicator={<Spinner />}
/>

Advanced Usage

Custom Form Layout

For complete control over your form's appearance:

<SmartForm schema={schema} onSubmit={handleSubmit}>
  {({ values, setValue, errors }) => (
    <div className="space-y-6">
      <div className="grid grid-cols-2 gap-4">
        <div>
          <label>First Name</label>
          <input
            value={values.firstName}
            onChange={(e) => setValue('firstName', e.target.value)}
            className="custom-input"
          />
          {errors.firstName && <span>{errors.firstName}</span>}
        </div>
        
        <div>
          <label>Last Name</label>
          <input
            value={values.lastName}
            onChange={(e) => setValue('lastName', e.target.value)}
            className="custom-input"
          />
          {errors.lastName && <span>{errors.lastName}</span>}
        </div>
      </div>

      <button type="submit">Submit</button>
    </div>
  )}
</SmartForm>

Accessing Form Values

{({ values, setValue, errors }) => (
  <>
    <input
      value={values.email}
      onChange={(e) => setValue('email', e.target.value)}
    />
    
    {/* Conditional rendering based on values */}
    {values.country === 'us' && (
      <input
        value={values.state}
        onChange={(e) => setValue('state', e.target.value)}
      />
    )}
    
    {/* Display errors */}
    {errors.email && (
      <span className="text-red-500">{errors.email}</span>
    )}
  </>
)}

Complete Examples

Login Form

import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';

function LoginForm() {
  const handleLogin = async (values) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(values),
    });
    // Handle response
  };

  return (
    <SmartForm
      schema={{
        email: {
          type: 'email',
          label: 'Email',
          required: true,
        },
        password: {
          type: 'password',
          label: 'Password',
          required: true,
          min: 8,
        },
        remember: {
          type: 'checkbox',
          checkboxLabel: 'Remember me',
        },
      }}
      onSubmit={handleLogin}
      buttonTitle="Sign In"
      formClassname="max-w-md mx-auto p-6 bg-white rounded-lg shadow"
    />
  );
}

Registration Form

import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';

function RegistrationForm() {
  return (
    <SmartForm
      schema={{
        fullName: {
          type: 'string',
          label: 'Full Name',
          required: true,
        },
        email: {
          type: 'email',
          label: 'Email',
          required: true,
          pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
        },
        password: {
          type: 'password',
          label: 'Password',
          required: true,
          min: 8,
        },
        age: {
          type: 'number',
          label: 'Age',
          required: true,
          min: 18,
        },
        country: {
          type: 'select',
          label: 'Country',
          required: true,
          placeholder: 'Select your country',
          options: [
            { value: 'us', label: 'United States' },
            { value: 'uk', label: 'United Kingdom' },
            { value: 'ca', label: 'Canada' },
          ],
        },
        terms: {
          type: 'checkbox',
          label: 'Terms',
          checkboxLabel: 'I agree to the terms and conditions',
          required: true,
        },
      }}
      onSubmit={(values) => {
        console.log('Registration:', values);
      }}
      buttonTitle="Create Account"
      formClassname="max-w-lg mx-auto p-8 bg-white rounded-xl shadow-lg"
    />
  );
}

Contact Form with Custom Layout

import { SmartForm } from 'smart-form';
import 'smart-form/dist/styles.css';

function ContactForm() {
  return (
    <SmartForm
      schema={{
        name: { type: 'string', label: 'Name', required: true },
        email: { type: 'email', label: 'Email', required: true },
        subject: { type: 'string', label: 'Subject', required: true },
        message: { type: 'textarea', label: 'Message', required: true, max: 500 },
      }}
      onSubmit={(values) => console.log(values)}
    >
      {({ values, setValue, errors }) => (
        <div className="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow">
          <h2 className="text-2xl font-bold mb-6">Contact Us</h2>
          
          <div className="grid grid-cols-2 gap-4 mb-4">
            <div>
              <label className="block text-sm font-medium mb-1">Name</label>
              <input
                value={values.name}
                onChange={(e) => setValue('name', e.target.value)}
                className="w-full px-3 py-2 border rounded-lg"
              />
              {errors.name && <span className="text-red-500 text-xs">{errors.name}</span>}
            </div>
            
            <div>
              <label className="block text-sm font-medium mb-1">Email</label>
              <input
                value={values.email}
                onChange={(e) => setValue('email', e.target.value)}
                className="w-full px-3 py-2 border rounded-lg"
              />
              {errors.email && <span className="text-red-500 text-xs">{errors.email}</span>}
            </div>
          </div>

          <div className="mb-4">
            <label className="block text-sm font-medium mb-1">Subject</label>
            <input
              value={values.subject}
              onChange={(e) => setValue('subject', e.target.value)}
              className="w-full px-3 py-2 border rounded-lg"
            />
            {errors.subject && <span className="text-red-500 text-xs">{errors.subject}</span>}
          </div>

          <div className="mb-6">
            <label className="block text-sm font-medium mb-1">Message</label>
            <textarea
              value={values.message}
              onChange={(e) => setValue('message', e.target.value)}
              rows={5}
              className="w-full px-3 py-2 border rounded-lg"
            />
            {errors.message && <span className="text-red-500 text-xs">{errors.message}</span>}
          </div>

          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700"
          >
            Send Message
          </button>
        </div>
      )}
    </SmartForm>
  );
}

API Reference

FormProps

| Prop | Type | Required | Description | |------|------|----------|-------------| | schema | FormSchema | ✅ | Form field definitions | | onSubmit | (values) => void | ✅ | Submit handler | | children | (params) => ReactNode | ❌ | Custom render function | | formClassname | string | ❌ | Form element class | | className | string | ❌ | Field wrapper class | | labelClassname | string | ❌ | Label class | | inputClassname | string | ❌ | Input wrapper class | | buttonClassname | string | ❌ | Button class | | buttonTitle | string | ❌ | Button text (default: "Submit") | | buttonLoading | boolean | ❌ | Loading state | | buttonDisabled | boolean | ❌ | Disabled state | | loadingIndicator | ReactNode | ❌ | Custom loading indicator |

Field Schema

type FieldSchema = {
  type?: 'string' | 'email' | 'number' | 'password' | 'textarea' | 
         'checkbox' | 'select' | 'date' | 'time';
  label?: string;
  required?: boolean;
  min?: number;
  max?: number;
  pattern?: RegExp;
  defaultValue?: string | number | boolean;
  disabled?: boolean;
  icon?: ReactNode;
  
  // Password specific
  passwordToggler?: (visible: boolean) => ReactNode;
  
  // Checkbox specific
  checkboxLabel?: string;
  
  // Select specific
  options?: Array<{ value: string | number; label: string; icon?: ReactNode }>;
  placeholder?: string;
};

TypeScript Support

SmartForm is fully typed. Your form values will be automatically inferred:

const schema = {
  email: { type: 'email' as const, required: true },
  age: { type: 'number' as const },
  terms: { type: 'checkbox' as const },
} as const;

<SmartForm
  schema={schema}
  onSubmit={(values) => {
    // values is typed as:
    // { email: string; age: string; terms: boolean }
    console.log(values.email); // ✅ TypeScript knows this is a string
  }}
/>

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

License

MIT © Samuel Ngene

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support


Made with ❤️ by Samuel Ngene