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 🙏

© 2025 – Pkg Stats / Ryan Hefner

feather-form

v1.0.2

Published

A lightweight and robust form component package with inbuilt validations for React

Readme

Feather Form🪶

A lightweight and robust form component package with built-in validations for React applications. Feather form provides fully typed, accessible, and customizable form components with integrated validation support.

npm version

Features

  • 🎯 9 Pre-built form components
  • ✅ Built-in validation schemas
  • 🎨 Fully customizable with Tailwind CSS
  • 📱 Responsive and accessible
  • 🔒 TypeScript support
  • 🪶 Lightweight (~20KB gzipped)

Installation

npm install feather-form

or

yarn add feather-form

or

pnpm add feather-form

Basic Usage

import { Input, useFormValidation } from 'feather-form';
import 'feather-form/styles.css'

const MyForm = () => {
  const { values, errors, handleChange, handleBlur } = useFormValidation({
    username: ''
  });

  return (
    <Input
      name="username"
      label="Username"
      value={values.username}
      onChange={handleChange}
      onBlur={handleBlur}
      error={errors.username}
      schema="name"
    />
  );
};

Components

Input

A flexible input component for text based inputs like name, email and password with built-in validation.

Props

| Prop | Type | Required | Description | |-------------|--------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | Input label | | value | string | Yes | Input value | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | No | Validation schema | | styleProps | object | No | Style customization |

Style Props

styleProps?: {
  container?: string;    // Wrapper div
  label?: string;        // Label element
  inputWrapper?: string; // Input container
  input?: string;        // Input element
  passwordToggle?: string; // Password visibility toggle
  errorText?: string;    // Error message
}

Example

<Input
  name="email"
  label="Email Address"
  value={values.email}
  onChange={handleChange}
  onBlur={handleBlur}
  error={errors.email}
  schema="email"
  styleProps={{
    container: "mb-4",
    label: "font-bold",
    input: "px-4 py-2"
  }}
/>

Select

A customizable select dropdown component

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | Select label | | value | string | Yes | Selected value | | options | string[] | Yes | Values to select from in dropdown | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | No | Validation schema | | styleProps | object | No | Style customization |

Style Props

styleProps?: {
  container?: string;        // Main container
  label?: string;         // Label element
  dropdownTrigger?: string; // Dropdown button
  dropdownList?: string;  // Options container
  option?: string;        // Individual option
  optionSelected?: string; // Selected option
  error?: string;         // Error message
}

Example

      <Select
        name="role"
        label="Job Role"
        error={errors.role}
        onBlur={handleBlur}
        onChange={handleChange}
        schema="select"
        value={values.role}
        options={["SDE1", "SDE2", "SDE3", "SA", "PE"]}
      />

Searchable Dropdown

A customizable searchable dropdown component

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | Select label | | value | string | Yes | Selected value | | options | string[] | Yes | Values to select from in dropdown | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | No | Validation schema | | styleProps | object | No | Style customization |

Style Props

styleProps?: {
  wrapper?: string;        // Main container
  label?: string;         // Label element
  dropdownTrigger?: string; // Dropdown button
  dropdownList?: string;  // Options container
  option?: string;        // Individual option
  optionSelected?: string; // Selected option
  error?: string;         // Error message
}

Example

      <SearchableDropdown
        name="department"
        label="Department"
        value={values.department}
        error={errors.department}
        onBlur={handleBlur}
        onChange={handleChange}
        options={[
          "Frontend",
          "Backend",
          "Database Management",
          "DevOps",
          "DevRel",
          "Mobile",
          "Human Resource",
        ]}
        schema="select"
      />

Radio Group

A flexible input component which allows you to render single-select radio buttons

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|--------------------| | name | string | Yes | Field identifier | | label | string | Yes | Group label | | options | string[] | Yes | Radio options | | value | string | Yes | Selected value | | onChange | function | Yes | Change handler | | error | string | No | Error message |

Style Props

styleProps?: {
  container?: string;       // Wrapper div
  label?: string;          // Group label
  optionsContainer?: string; // Container for radio options
  optionItem?: string;     // Individual radio item wrapper
  optionLabel?: string;    // Label for each radio option  
  radioInput?: string;     // Radio input element
  errorText?: string;      // Error message
}

Example

      <RadioGroup
        name="gender"
        label="Gender"
        options={["male", "female"]}
        value={values.gender}
        key="gender"
        onBlur={handleBlur}
        onChange={handleChange}
        error={errors.gender}
        schema="select"
      />

Text Area

A mulit-line text input useful for paragraph based content

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | TextArea label | | value | string | Yes | Input value | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | No | Validation schema |

Style Props

styleProps?: {
  container?: string;    // Wrapper div
  label?: string;       // Label element
  textArea?: string;    // Textarea element
  errorText?: string;   // Error message
}

Example

<TextArea
  name="coverLetter"
  label="Cover Letter"
  value={values.coverLetter}
  error={errors.coverLetter}
  schema="textarea"
  onBlur={handleBlur}
  onChange={handleChange}
  styleProps={{
    container: "mb-4",
    label: "font-bold",
    textArea: "p-2 h-32"
  }}
/>

Date Picker

A date picker component with built-in validation requiring date to be selected before submitting

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | TextArea label | | value | string | Yes | Input value | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | No | Validation schema |

Style Props

styleProps?: {
  container?: string;      // Wrapper div
  label?: string;         // Label element
  inputContainer?: string; // Date input container
  input?: string;         // Date input element
  errorText?: string;     // Error message
}

Example

<DatePicker
  name="dateOfBirth"
  label="Date of Birth"
  value={values.dateOfBirth}
  onChange={handleChange}
  onBlur={handleBlur}
  error={errors.dateOfBirth}
  styleProps={{
    container: "mb-4",
    label: "font-bold",
    input: "p-2"
  }}
/>

Time Picker

A time picker component with built-in validation requiring date to be selected before submitting

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | Time picker label | | value | string | Yes | Selected time | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message |

Style Props

styleProps?: {
  container?: string;      // Wrapper div
  label?: string;         // Label element
  inputContainer?: string; // Time input container
  input?: string;         // Time input element
  errorText?: string;     // Error message
}

Example

<TimePicker
  name="loginTime"
  label="Login Time"
  value={values.loginTime}
  onChange={handleChange}
  onBlur={handleBlur}
  error={errors.loginTime}
  styleProps={{
    container: "mb-4",
    label: "font-bold",
    input: "p-2"
  }}
/>

File Picker

A file picker component with built-in validation requiring the file to be selected before submitting

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | name | string | Yes | Field identifier | | label | string | Yes | File picker label | | value | File | Yes | Selected file | | onChange | function | Yes | Change handler | | onBlur | function | Yes | Blur handler | | error | string | No | Error message | | schema | string | Yes | Validation schema |

Style Props

styleProps?: {
  container?: string;    // Wrapper div
  label?: string;       // Label element
  input?: string;       // File input element
  button?: string;      // Upload button
  fileName?: string;    // Selected filename text
  errorText?: string;   // Error message
}

Example

<FilePicker
  name="resume"
  label="Resume"
  value={values.resume}
  onChange={handleChange}
  onBlur={handleBlur}
  error={errors.resume}
  schema="file"
  styleProps={{
    container: "mb-4",
    label: "font-bold",
    button: "bg-blue-500 text-white px-4 py-2 rounded"
  }}
/>

Submit Button

The submit button component of our useFormValidation component, it's responsible for submitting the form

Props

| Prop | Type | Required | Description | |-------------|-----------|----------|---------------------| | disabled | boolean | Yes | Button disabled state | | label | string | No | Button text | | styleProps | object | No | Style customization |

Style Props

styleProps?: {
  base?: string;      // Base button styles
  enabled?: string;   // Enabled state styles
  disabled?: string;  // Disabled state styles
}

Example

<SubmitButton
  disabled={hasErrors()}
  label="Submit Form"
  styleProps={{
    base: "p-2 rounded text-white w-full",
    enabled: "bg-blue-500 hover:bg-blue-600",
    disabled: "bg-gray-400 cursor-not-allowed"
  }}
/>

Validation Schema

  • name: Validates names (letters only)
  • email: Validates email addresses
  • password: Minimum 8 characters, including letters, numbers, and special characters
  • select: Validates a value from the dropdown has been selected
  • radio: Validates one radio button is selected
  • textarea: Validates text area isn't empty
  • file: Validates file has been selected
  • date: Validates date selections

Complete Example

import React from "react";
import {
  DatePicker,
  FilePicker,
  Input,
  RadioGroup,
  SearchableDropdown,
  Select,
  SubmitButton,
  TextArea,
  TimePicker,
  useFormValidation
} from "feather-form";

import 'feather-form/styles.css'

const App = () => {
  const { values, errors, handleChange, handleBlur, hasErrors } =
    useFormValidation({
      name: "",
      email: "",
      password: "",
      coverLetter: "",
      gender: "",
      role: "",
      department: "",
      resume: "",
      dateOfBirth: "",
      loginTime: "",
    });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log("Form submitted:", values);
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-200 py-12 px-4 sm:px-6 lg:px-8">
      <div className="max-w-4xl mx-auto">
        <div className="text-center mb-12">
          <h1 className="text-4xl font-bold text-gray-900 mb-2">
            Feather From🪶
          </h1>
          <p className="text-gray-600">
            Elegant and Responsive Form Components
          </p>
        </div>

        <form
          className="bg-white rounded-2xl shadow-xl p-8 space-y-8"
          onSubmit={(e) => handleSubmit(e)}
        >
          {/* Personal Information Section */}
          <section className="space-y-6">
            <h2 className="text-xl font-semibold text-gray-800 pb-2 border-b">
              Personal Information
            </h2>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
              <Input
                name="name"
                label="Full Name"
                value={values.name}
                error={errors.name}
                onChange={handleChange}
                onBlur={handleBlur}
                schema="name"
              />
              <Input
                name="email"
                label="Email Address"
                value={values.email}
                error={errors.email}
                onChange={handleChange}
                onBlur={handleBlur}
                schema="email"
              />
            </div>
            <Input
              name="password"
              label="Password"
              value={values.password}
              error={errors.password}
              onChange={handleChange}
              onBlur={handleBlur}
              schema="password"
            />
          </section>

          {/* Professional Details Section */}
          <section className="space-y-6">
            <h2 className="text-xl font-semibold text-gray-800 pb-2 border-b">
              Professional Details
            </h2>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
              <Select
                name="department"
                label="Department"
                value={values.department}
                error={errors.department}
                onChange={handleChange}
                onBlur={handleBlur}
                options={["Engineering", "Design", "Marketing", "Sales"]}
                schema="select"
              />
              <SearchableDropdown
                name="role"
                label="Role"
                value={values.role}
                error={errors.role}
                onChange={handleChange}
                onBlur={handleBlur}
                options={["Developer", "Designer", "Manager", "Director"]}
                schema="select"
              />
            </div>
            <TextArea
              name="coverLetter"
              label="Cover Letter"
              value={values.coverLetter}
              error={errors.coverLetter}
              onChange={handleChange}
              onBlur={handleBlur}
              schema="textarea"
            />
          </section>

          {/* Additional Information Section */}
          <section className="space-y-6">
            <h2 className="text-xl font-semibold text-gray-800 pb-2 border-b">
              Additional Information
            </h2>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
              <DatePicker
                name="dateOfBirth"
                label="Date of Birth"
                value={values.dateOfBirth}
                error={errors.dateOfBirth}
                onChange={handleChange}
                onBlur={handleBlur}
              />
              <TimePicker
                name="loginTime"
                label="Preferred Login Time"
                value={values.loginTime}
                error={errors.loginTime}
                onChange={handleChange}
                onBlur={handleBlur}
              />
            </div>
            <RadioGroup
              name="gender"
              label="Gender"
              value={values.gender}
              error={errors.gender}
              onChange={handleChange}
              onBlur={handleBlur}
              options={["Male", "Female", "Other"]}
              schema="radio"
            />
            <FilePicker
              name="resume"
              label="Upload Resume"
              value={values.resume}
              error={errors.resume}
              onChange={handleChange}
              onBlur={handleBlur}
              schema="file"
            />
          </section>

          <div className="pt-6">
            <SubmitButton label="Submit Application" disabled={hasErrors()} />
          </div>
        </form>
      </div>
    </div>
  );
};

export default App;