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

@algodomain/smart-forms

v0.1.12

Published

A powerful React form framework with smart fields, multi-tab support, and seamless API integration

Readme

@algodomain/smart-forms

A powerful, flexible React form framework with smart fields, multi-tab support, built-in validation, and seamless API integration.

npm version License: MIT

Built with TypeScript, Tailwind CSS, Zod validation, and shadcn/ui components.


📋 Table of Contents


✨ Features

  • 🎯 Smart Fields - Pre-built, validated form fields with consistent UX
  • 📑 Multi-Tab Forms - Create complex forms with tabbed navigation and progress tracking
  • Built-in Validation - Powered by Zod for type-safe validation
  • 🔄 API Integration - Automatic form submission with authentication support
  • 🔗 Query Params - Include URL query parameters in form submissions
  • 💾 Auto-Save - LocalStorage support for draft saving
  • 🎛️ Conditional Disabling - Disable fields and submit buttons based on form data
  • 🎨 Customizable - Full theme control via CSS variables
  • 🌙 Dark Mode - Built-in support for light and dark themes
  • Accessible - Built on Radix UI primitives
  • 📦 Tree-Shakeable - Import only what you need
  • 🔒 Type-Safe - Full TypeScript support

📦 Installation

1. Install the Package

npm install @algodomain/smart-forms
# or
yarn add @algodomain/smart-forms
# or
pnpm add @algodomain/smart-forms

2. Install Peer Dependencies

npm install react react-dom tailwindcss zod react-toastify date-fns lucide-react

3. Import Styles

Add to your main entry file (main.tsx or index.tsx):

import '@algodomain/smart-forms/style.css'
import 'react-toastify/dist/ReactToastify.css'

4. Configure Tailwind CSS (v4)

Add @source directives to scan the package for Tailwind classes:

/* src/index.css */
@import "tailwindcss";

@source "../node_modules/@algodomain/smart-forms/dist/index.js";
@source "../node_modules/@algodomain/smart-forms/dist/index.cjs";

@import "@algodomain/smart-forms/style.css";

🚀 Quick Start

Basic Form Example

import { SmartForm } from '@algodomain/smart-forms'
import { SmartInput, SmartSelect } from '@algodomain/smart-forms/fields'
import { FieldEmail } from '@algodomain/smart-forms/opinionated'
import { z } from 'zod'

function RegistrationForm() {
  return (
    <SmartForm
      api="https://api.example.com/register"
      method="POST"
      submitButtonText="Sign Up"
      onSuccess={(data) => console.log('Success!', data)}
      onError={(error) => console.error('Error:', error)}
    >
      <SmartInput
        field="name"
        label="Full Name"
        validation={z.string().min(3, 'Name must be at least 3 characters')}
        required
      />
      
      <FieldEmail field="email" required />
      
      <SmartSelect
        field="role"
        label="Role"
        options={[
          { value: 'user', label: 'User' },
          { value: 'admin', label: 'Admin' }
        ]}
        required
      />
    </SmartForm>
  )
}

Multi-Tab Form Example

import { MultiTabSmartForm, Tab } from '@algodomain/smart-forms'
import { SmartInput, SmartTags } from '@algodomain/smart-forms/fields'
import { z } from 'zod'

function JobPostingForm() {
  return (
    <MultiTabSmartForm
      api="/api/jobs"
      method="POST"
      showProgressBar={true}
      showTabNumbers={true}
      onSuccess={(data) => console.log('Job created:', data)}
    >
      <Tab title="Basic Info">
        <SmartInput 
          field="jobTitle" 
          label="Job Title" 
          validation={z.string().min(3)}
          required 
        />
      </Tab>
      
      <Tab title="Requirements">
        <SmartTags 
          field="skills" 
          label="Required Skills"
          validation={z.array(z.string()).min(1)}
          required 
        />
      </Tab>
    </MultiTabSmartForm>
  )
}

📝 Form Components

SmartForm

Single-page form with automatic submission handling.

Usage:

<SmartForm
  api="/api/endpoint"
  method="POST"
  onSuccess={(data) => console.log(data)}
  onError={(error) => console.error(error)}
>
  {/* Your fields here */}
</SmartForm>

MultiTabSmartForm

Multi-step form with tabs and progress tracking.

Usage:

<MultiTabSmartForm
  api="/api/submit"
  showProgressBar={true}
  showTabNumbers={true}
  animateTabs={false}
  tabType="default"
>
  <Tab title="Step 1" onNext={async () => { /* custom logic */ }}>
    {/* Fields */}
  </Tab>
  <Tab title="Step 2" processingOverlay={<LoadingSpinner />}>
    {/* Fields */}
  </Tab>
</MultiTabSmartForm>

Additional Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | animateTabs | boolean | false | Enable tab transition animations | | tabType | 'underline' \| 'default' | 'default' | Tab styling variant | | disableManualTabSwitch | boolean | false | Prevent manual clicking on future tabs. Users can still navigate forward via Next button and go back to previous tabs. |

Tab Component Props:

| Prop | Type | Description | |------|------|-------------| | title | string | Required. Tab title | | children | ReactNode | Tab content | | onNext | () => Promise<void> \| void | Callback executed before moving to next tab. Can be async. If it throws, navigation is prevented. | | processingOverlay | ReactNode | Overlay shown while onNext is processing |

BaseSmartForm

Low-level form component for custom layouts.

Usage:

<BaseSmartForm api="/api/submit">
  <div className="custom-layout">
    <SmartInput field="name" label="Name" />
    <CustomSubmitButton />
  </div>
</BaseSmartForm>

FormFieldGroup

A layout component for grouping form fields horizontally with responsive spacing. Fields automatically wrap to multiple rows on smaller screens.

Usage:

import { FormFieldGroup } from '@algodomain/smart-forms'
import { SmartInput, SmartSelect } from '@algodomain/smart-forms/fields'

<SmartForm api="/api/submit">
  <FormFieldGroup>
    <SmartInput field="firstName" label="First Name" />
    <SmartInput field="lastName" label="Last Name" />
  </FormFieldGroup>
  
  <FormFieldGroup>
    <SmartInput field="city" label="City" />
    <SmartSelect field="state" label="State" options={stateOptions} />
    <SmartInput field="zipCode" label="Zip Code" />
  </FormFieldGroup>
</SmartForm>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | - | Required. Form field components to group | | className | string | '' | Additional CSS classes to apply |

Features:

  • Responsive flex layout with automatic wrapping
  • Spacing: gap-2 on mobile, gap-4 on medium screens and above
  • Bottom margin: mb-4 for consistent spacing between groups

Example: Address Form with Grouped Fields

<SmartForm api="/api/submit">
  <FormFieldGroup>
    <SmartInput field="streetAddress" label="Street Address" required />
  </FormFieldGroup>
  
  <FormFieldGroup>
    <SmartInput field="city" label="City" required />
    <SmartSelect field="state" label="State" options={states} required />
    <SmartInput field="zipCode" label="Zip Code" required />
  </FormFieldGroup>
  
  <FormFieldGroup className="gap-6">
    <SmartInput field="country" label="Country" required />
    <SmartSelect field="phoneCode" label="Phone Code" options={phoneCodes} />
  </FormFieldGroup>
</SmartForm>

🎯 Field Components

All field components are imported from @algodomain/smart-forms/fields:

import {
  SmartInput,
  SmartSelect,
  SmartCheckbox,
  SmartRadioGroup,
  SmartDatePicker,
  SmartTags,
  SmartSlider,
  SmartDualRangeSlider,
  SmartCombobox,
  SmartBasicRichTextbox,
  SmartFileUpload,
  SmartAutoSuggestTags
} from '@algodomain/smart-forms/fields'

SmartInput

Text input with support for multiple types.

<SmartInput
  field="username"
  label="Username"
  type="text"
  placeholder="Enter username"
  validation={z.string().min(3).max(20)}
  required
/>

Supported types: text, email, password, tel, number, textarea

SmartSelect

Dropdown select field.

<SmartSelect
  field="country"
  label="Country"
  options={[
    { value: 'us', label: 'United States' },
    { value: 'ca', label: 'Canada' }
  ]}
  required
/>

SmartCheckbox

Checkbox with label.

<SmartCheckbox
  field="terms"
  label="I agree to terms"
  validation={z.boolean().refine(val => val === true)}
  required
/>

SmartRadioGroup

Radio button group.

<SmartRadioGroup
  field="gender"
  label="Gender"
  options={[
    { value: 'male', label: 'Male' },
    { value: 'female', label: 'Female' }
  ]}
  alignment="horizontal"
  required
/>

SmartDatePicker

Date picker with calendar UI.

<SmartDatePicker
  field="birthDate"
  label="Date of Birth"
  validation={z.date()}
  required
/>

SmartTags

Tag input for multiple values.

<SmartTags
  field="skills"
  label="Skills"
  placeholder="Type and press Enter"
  validation={z.array(z.string()).min(1)}
  maxTags={10}
  required
/>

SmartSlider

Single value slider.

<SmartSlider
  field="experience"
  label="Years of Experience"
  min={0}
  max={40}
  step={1}
  showValue={true}
/>

SmartDualRangeSlider

Min/max range slider.

<SmartDualRangeSlider
  minField="minSalary"
  maxField="maxSalary"
  label="Salary Range"
  min={0}
  max={200000}
  step={5000}
/>

SmartCombobox

Searchable combo box.

<SmartCombobox
  field="technology"
  label="Technology"
  options={[
    { value: 'react', label: 'React' },
    { value: 'vue', label: 'Vue' }
  ]}
  allowCustom={true}
/>

SmartBasicRichTextbox

Basic rich text editor.

<SmartBasicRichTextbox
  field="description"
  label="Description"
  minHeight="150px"
  validation={z.string().min(50)}
  required
/>

SmartFileUpload

File upload field.

<SmartFileUpload
  field="resume"
  label="Upload Resume"
  accept=".pdf,.doc,.docx"
  maxSize={5 * 1024 * 1024}
  required
/>

🎨 Opinionated Fields

Pre-configured fields with built-in validation. Import from @algodomain/smart-forms/opinionated:

import {
  FieldEmail,
  FieldPassword,
  FieldConfirmPassword,
  FieldPhone,
  FieldFirstName,
  FieldLastName,
  FieldFullName,
  FieldAge,
  FieldStreetAddress,
  FieldCity,
  FieldState,
  FieldZipCode,
  FieldMessage,
  FieldComments
} from '@algodomain/smart-forms/opinionated'

Usage:

<FieldEmail field="email" required />
<FieldPassword field="password" required />
<FieldPhone field="phone" />
<FieldFullName field="fullName" required />

⚙️ Configuration Reference

Form Component Props

All form components (SmartForm, MultiTabSmartForm, BaseSmartForm) support these props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | api | string | - | API endpoint for form submission | | method | 'POST' \| 'PUT' \| 'PATCH' | 'POST' | HTTP method | | submitButtonText | string | 'Submit' | Submit button text | | submitButtonIcon | ReactNode | - | Icon for submit button | | allowSaveDraft | boolean | false | Enable draft saving | | saveDraftApi | string | - | API endpoint for saving drafts | | enableLocalStorage | boolean | false | Save drafts to localStorage | | storageKey | string | 'smart-form-data' | Key for localStorage | | logFormData | boolean | false | Log form data to console | | onSuccess | (data: unknown) => void | - | Success callback | | onError | (error: unknown) => void | - | Error callback | | transformData | (data: any) => any | - | Transform data before submission | | className | string | 'max-w-2xl mx-auto p-6 bg-white' | Container CSS class | | title | string | - | Form title | | subTitle | string | - | Form subtitle | | logo | ReactNode | - | Logo to display | | footer | ReactNode | - | Footer content | | initialData | Record<string, unknown> | {} | Initial form values | | showReset | boolean | false | Show reset button | | showProgressBar | boolean | true | Display progress bar (MultiTab only) | | showTabNumbers | boolean | true | Show tab numbers (MultiTab only) | | animateTabs | boolean | false | Enable tab animations (MultiTab only) | | tabType | 'underline' \| 'default' | 'default' | Tab styling variant (MultiTab only) | | disableManualTabSwitch | boolean | false | Prevent manual clicking on future tabs (MultiTab only) | | authentication | AuthenticationConfig | - | Authentication configuration | | includeQueryParams | boolean | false | Include URL query parameters | | queryParamsToInclude | string[] | - | Filter specific query params | | submitDisabled | boolean \| ((formData: any) => boolean) | false | Disable submit button statically or conditionally based on form data | | children | ReactNode | - | Form content |

Authentication Configuration

interface AuthenticationConfig {
  enable: boolean                    // Enable authentication
  refreshTokenEndpoint?: string      // Token refresh endpoint
  accessTokenKey?: string            // localStorage key (default: 'accessToken')
  refreshTokenKey?: string           // localStorage key (default: 'refreshToken')
}

Usage:

<SmartForm
  authentication={{
    enable: true,
    refreshTokenEndpoint: "/api/auth/refresh",
    accessTokenKey: "accessToken",
    refreshTokenKey: "refreshToken"
  }}
>
  {/* Fields */}
</SmartForm>

Common Field Props

All field components support these props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | field | string | - | Required. Field identifier | | label | string | - | Field label | | required | boolean | false | Mark as required | | validation | ZodSchema | - | Zod validation schema | | defaultValue | any | - | Initial value | | placeholder | string | - | Placeholder text | | className | string | - | Custom CSS class | | info | string | - | Info tooltip text | | subLabel | string | - | Additional helper text | | disabled | boolean \| ((formData: any) => boolean) | false | Disable field statically or conditionally based on form data | | hidden | boolean \| ((formData: any) => boolean) | false | Hide field conditionally based on form data |

Field-Specific Props

SmartInput

| Prop | Type | Default | Allowed Values | |------|------|---------|----------------| | type | string | 'text' | text, email, password, tel, number, textarea |

SmartSelect / SmartCombobox

| Prop | Type | Default | Description | |------|------|---------|-------------| | options | Array<{value: string, label: string}> | [] | Select options | | allowCustom | boolean | false | Allow custom values (Combobox only) |

SmartRadioGroup

| Prop | Type | Default | Allowed Values | |------|------|---------|----------------| | options | Array<{value: string, label: string}> | [] | Radio options | | alignment | string | 'vertical' | horizontal, vertical |

SmartTags

| Prop | Type | Default | Description | |------|------|---------|-------------| | maxTags | number | - | Maximum number of tags | | maxLength | number | - | Max length per tag | | minLength | number | - | Min length per tag | | allowDuplicates | boolean | true | Allow duplicate tags |

SmartSlider

| Prop | Type | Default | Description | |------|------|---------|-------------| | min | number | 0 | Minimum value | | max | number | 100 | Maximum value | | step | number | 1 | Step increment | | showValue | boolean | true | Display current value | | valueFormatter | (value: number) => string | - | Format displayed value |

SmartDualRangeSlider

| Prop | Type | Default | Description | |------|------|---------|-------------| | minField | string | - | Required. Field name for min value | | maxField | string | - | Required. Field name for max value | | min | number | 0 | Minimum value | | max | number | 100 | Maximum value | | step | number | 1 | Step increment | | showValues | boolean | true | Display values | | valueFormatter | (value: number) => string | - | Format displayed value |

SmartBasicRichTextbox

| Prop | Type | Default | Description | |------|------|---------|-------------| | minHeight | string | '150px' | Minimum height | | maxHeight | string | '400px' | Maximum height |

SmartFileUpload

| Prop | Type | Default | Description | |------|------|---------|-------------| | accept | string | - | Accepted file types | | maxSize | number | - | Max file size in bytes |


🚀 Advanced Features

Disable Manual Tab Switching

Control whether users can manually click on future tabs. When enabled, users must complete each step sequentially using the Next button, but can still go back to previous tabs.

Usage:

<MultiTabSmartForm
  api="/api/submit"
  disableManualTabSwitch={true}
>
  <Tab title="Step 1">
    <SmartInput field="name" label="Name" required />
  </Tab>
  
  <Tab title="Step 2">
    <SmartInput field="email" label="Email" required />
  </Tab>
  
  <Tab title="Step 3">
    <SmartInput field="phone" label="Phone" required />
  </Tab>
</MultiTabSmartForm>

Behavior:

  • When disableManualTabSwitch={true}:
    • Users cannot click on tabs ahead of the current tab
    • Future tabs appear disabled (grayed out)
    • Users can click on previous tabs to go back
    • Users can navigate forward using the Next button (after validation)
  • When disableManualTabSwitch={false} (default):
    • Users can freely click on any tab
    • All tabs are clickable

Use Cases:

  • Enforce sequential completion of multi-step forms
  • Ensure users complete each step before proceeding
  • Prevent skipping validation steps
  • Guide users through a structured workflow

Tab Callbacks and Processing Overlays

In MultiTabSmartForm, you can add custom logic before moving to the next tab using the onNext callback. This is useful for async operations like API calls or data processing.

Basic Usage:

<MultiTabSmartForm api="/api/submit">
  <Tab 
    title="Step 1"
    onNext={async () => {
      // Custom validation or API call
      const response = await fetch('/api/validate-step1')
      if (!response.ok) {
        throw new Error('Validation failed')
      }
    }}
  >
    <SmartInput field="name" label="Name" required />
  </Tab>
  
  <Tab title="Step 2">
    <SmartInput field="email" label="Email" required />
  </Tab>
</MultiTabSmartForm>

With Processing Overlay:

import { LoadingSpinner } from '@algodomain/smart-forms'

<MultiTabSmartForm api="/api/submit">
  <Tab 
    title="Processing Step"
    onNext={async () => {
      // Long-running operation
      await processData()
    }}
    processingOverlay={
      <div className="flex flex-col items-center gap-2">
        <LoadingSpinner className="h-8 w-8" />
        <p>Processing your data...</p>
      </div>
    }
  >
    <SmartInput field="data" label="Data" required />
  </Tab>
</MultiTabSmartForm>

Error Handling:

If onNext throws an error, navigation to the next tab is prevented and the error message is displayed:

<Tab 
  title="Step 1"
  onNext={async () => {
    const result = await validateData()
    if (!result.valid) {
      throw new Error(result.message) // Navigation prevented, error shown
    }
  }}
>
  {/* Fields */}
</Tab>

External Forms Integration

You can integrate external forms (forms outside the MultiTabSmartForm component) with the tab system for validation and navigation.

Method 1: Manual Registration (Recommended for known fields)

import { useExternalFormFields } from '@algodomain/smart-forms'

function ExternalForm() {
  // Register fields for tab index 0
  useExternalFormFields(['name', 'email', 'phone'], 0)
  
  return (
    <div>
      <input name="name" />
      <input name="email" />
      <input name="phone" />
    </div>
  )
}

// In your MultiTabSmartForm
<MultiTabSmartForm api="/api/submit">
  <Tab title="Step 1">
    <ExternalForm />
  </Tab>
</MultiTabSmartForm>

Method 2: Auto-Detection (Recommended for dynamic fields)

import { useExternalTab, ExternalFieldProvider } from '@algodomain/smart-forms'
import { SmartInput } from '@algodomain/smart-forms/fields'

function ExternalForm() {
  const { registerField, ExternalFieldProvider } = useExternalTab()
  
  return (
    <ExternalFieldProvider registerField={registerField}>
      <SmartInput field="name" label="Name" />
      <SmartInput field="email" label="Email" />
    </ExternalFieldProvider>
  )
}

Method 3: Using TabIndexProvider

import { TabIndexProvider, useExternalFormFields } from '@algodomain/smart-forms'

function ExternalForm() {
  const tabIndexContext = useTabIndex()
  const tabIndex = tabIndexContext?.tabIndex ?? 0
  
  useExternalFormFields(['field1', 'field2'], tabIndex)
  
  return <div>{/* Your form */}</div>
}

// Wrap with TabIndexProvider
<TabIndexProvider tabIndex={0}>
  <ExternalForm />
</TabIndexProvider>

Submit Hooks

Register custom hooks to execute before form submission. Useful for file uploads or other async operations.

import { useSmartForm } from '@algodomain/smart-forms'

function FileUploadField() {
  const { registerSubmitHook, unregisterSubmitHook } = useSmartForm()
  const field = 'resume'
  
  useEffect(() => {
    const key = `upload-${field}`
    
    const uploadHook = async () => {
      // Upload file before form submission
      await uploadFile(file)
    }
    
    registerSubmitHook(key, uploadHook)
    
    return () => {
      unregisterSubmitHook(key)
    }
  }, [field, file])
  
  return <input type="file" />
}

Conditional Field Disabling

Disable fields or submit buttons based on form data values. Supports both static boolean values and dynamic functions.

Disable Fields Conditionally

Static Disable:

<SmartInput
  field="username"
  label="Username"
  disabled={true}
/>

Conditional Disable Based on Form Data:

<SmartForm api="/api/submit">
  <SmartCombobox
    field="country"
    label="Country"
    options={[
      { value: 'us', label: 'United States' },
      { value: 'ca', label: 'Canada' }
    ]}
    placeholder="Please select the country"
  />
  
  <SmartCombobox
    field="state"
    label="State"
    options={stateOptions}
    disabled={(formData) => !formData.country || formData.country === ""}
  />
  
  <SmartInput
    field="zipCode"
    label="Zip Code"
    disabled={(formData) => !formData.state}
  />
</SmartForm>

How it works:

  • The disabled function receives the current formData object
  • It re-evaluates automatically when form data changes
  • Returns true to disable, false to enable
  • Empty strings, null, and undefined are all falsy in JavaScript

Example: Disable until checkbox is checked:

<SmartForm api="/api/submit">
  <SmartCheckbox
    field="agreeToTerms"
    label="I agree to the terms and conditions"
  />
  
  <SmartInput
    field="comments"
    label="Additional Comments"
    disabled={(formData) => !formData.agreeToTerms}
  />
</SmartForm>

Example: Complex conditional logic:

<SmartInput
  field="phone"
  label="Phone Number"
  disabled={(formData) => {
    // Disable if email is not provided OR if user is under 18
    return !formData.email || (formData.age && formData.age < 18)
  }}
/>

Disable Submit Button Conditionally

Static Disable:

<SmartForm
  api="/api/submit"
  submitDisabled={true}
>
  {/* Fields */}
</SmartForm>

Conditional Disable:

<SmartForm
  api="/api/submit"
  submitDisabled={(formData) => !formData.agreeToTerms}
>
  <SmartInput field="name" label="Name" required />
  <SmartCheckbox
    field="agreeToTerms"
    label="I agree to the terms"
  />
</SmartForm>

Example: Disable submit until all required fields are filled:

<SmartForm
  api="/api/submit"
  submitDisabled={(formData) => {
    return !formData.name || !formData.email || !formData.agreeToTerms
  }}
>
  <SmartInput field="name" label="Name" required />
  <SmartInput field="email" label="Email" required />
  <SmartCheckbox field="agreeToTerms" label="I agree" required />
</SmartForm>

Note: The submit button is automatically disabled when isLoading is true. The submitDisabled prop is combined with the loading state: disabled={isLoading || isSubmitDisabled}.

Combining Disabled and Hidden

You can use both disabled and hidden together for different scenarios:

<SmartForm api="/api/submit">
  <SmartSelect
    field="userType"
    label="User Type"
    options={[
      { value: 'admin', label: 'Admin' },
      { value: 'user', label: 'User' }
    ]}
  />
  
  {/* Hidden for non-admin users */}
  <SmartInput
    field="adminCode"
    label="Admin Code"
    hidden={(formData) => formData.userType !== 'admin'}
  />
  
  {/* Disabled (but visible) for admin users */}
  <SmartInput
    field="userNotes"
    label="User Notes"
    disabled={(formData) => formData.userType === 'admin'}
  />
</SmartForm>

Hide Fields Conditionally

Hide fields completely from the form based on form data. When a field is hidden, it's not rendered at all (unlike disabled which shows the field but makes it non-interactive).

Static Hide:

<SmartInput
  field="optionalField"
  label="Optional Field"
  hidden={true}
/>

Conditional Hide:

<SmartForm api="/api/submit">
  <SmartSelect
    field="accountType"
    label="Account Type"
    options={[
      { value: 'personal', label: 'Personal' },
      { value: 'business', label: 'Business' }
    ]}
  />
  
  {/* Only show business fields when account type is business */}
  <SmartInput
    field="businessName"
    label="Business Name"
    hidden={(formData) => formData.accountType !== 'business'}
  />
  
  <SmartInput
    field="taxId"
    label="Tax ID"
    hidden={(formData) => formData.accountType !== 'business'}
  />
</SmartForm>

Example: Show/hide based on checkbox:

<SmartForm api="/api/submit">
  <SmartCheckbox
    field="hasSpecialRequirements"
    label="I have special requirements"
  />
  
  <SmartInput
    field="specialRequirements"
    label="Please describe your requirements"
    type="textarea"
    hidden={(formData) => !formData.hasSpecialRequirements}
  />
</SmartForm>

Difference between disabled and hidden:

  • disabled: Field is visible but grayed out and non-interactive
  • hidden: Field is completely removed from the DOM (not rendered)

When to use each:

  • Use disabled when you want to show the field exists but is temporarily unavailable
  • Use hidden when you want to completely hide fields that aren't relevant to the current form state

All Supported Components

Both disabled and hidden props are available on all smart field components:

  • SmartInput
  • SmartSelect
  • SmartCombobox
  • SmartCheckbox
  • SmartRadioGroup
  • SmartDatePicker
  • SmartSlider
  • SmartDualRangeSlider
  • SmartTags
  • SmartFileUpload
  • SmartAutoSuggestTags
  • SmartBasicRichTextbox

Query Parameters

Automatically include URL query parameters in form submissions.

Include ALL query parameters:

<SmartForm
  api="/api/submit"
  includeQueryParams={true}
>
  <SmartInput field="name" label="Name" />
</SmartForm>

Include SPECIFIC query parameters:

<SmartForm
  api="/api/submit"
  includeQueryParams={true}
  queryParamsToInclude={['userId', 'companyId']}
>
  <SmartInput field="name" label="Name" />
</SmartForm>

Example:

  • URL: /form?userId=123&companyId=456
  • Form data: { name: "John" }
  • Submitted: { userId: "123", companyId: "456", name: "John" }

Behavior:

  • Form field values always override query params if names conflict
  • Works with both form submission and draft saving
  • If queryParamsToInclude is omitted, all query params are included

Draft Saving

Local Storage:

<SmartForm
  enableLocalStorage={true}
  storageKey="my-form-draft"
  allowSaveDraft={true}
>
  {/* Fields */}
</SmartForm>

API-based Draft:

<SmartForm
  allowSaveDraft={true}
  saveDraftApi="/api/drafts"
>
  {/* Fields */}
</SmartForm>

Data Transformation

Transform form data before submission:

<SmartForm
  transformData={(data) => ({
    ...data,
    skills: data.skills?.join(','),
    timestamp: new Date().toISOString()
  })}
>
  {/* Fields */}
</SmartForm>

Initial Values

Pre-populate form fields:

<SmartForm
  initialData={{
    name: 'John Doe',
    email: '[email protected]',
    country: 'us'
  }}
>
  {/* Fields */}
</SmartForm>

Authentication

Enable automatic Bearer token authentication:

<SmartForm
  authentication={{
    enable: true,
    refreshTokenEndpoint: "/api/auth/refresh"
  }}
>
  {/* Fields */}
</SmartForm>

The library will:

  1. Get access token from localStorage
  2. Include it as Authorization: Bearer <token>
  3. Automatically refresh if token expires
  4. Retry the request with new token

🧩 UI Components

FormHeader

Display form title, subtitle, and logo.

import { FormHeader } from '@algodomain/smart-forms'

<FormHeader 
  title="Registration Form"
  subTitle="Please fill in your details"
  logo={<img src="/logo.png" alt="Logo" />}
/>

Section

Section divider with optional text.

import { Section } from '@algodomain/smart-forms'

<Section text="Personal Information" />

Footer

Footer component for form content.

import { Footer } from '@algodomain/smart-forms'

<Footer>
  <p className="text-sm text-gray-500">
    By submitting, you agree to our terms and conditions.
  </p>
</Footer>

ToastContainerWrapper

Toast notification container (automatically included in forms).

import { ToastContainerWrapper } from '@algodomain/smart-forms'

<ToastContainerWrapper />

🔘 Button Components

LoadingSpinner

Loading spinner component.

import { LoadingSpinner } from '@algodomain/smart-forms'

<LoadingSpinner className="h-6 w-6" />

SubmitButton

Submit button with loading state.

import { SubmitButton } from '@algodomain/smart-forms'

<SubmitButton
  onClick={handleSubmit}
  disabled={isLoading}
  isLoading={isLoading}
>
  Submit Form
</SubmitButton>

DraftSaveButton

Draft save button.

import { DraftSaveButton } from '@algodomain/smart-forms'

<DraftSaveButton
  onClick={handleSaveDraft}
  disabled={isDraftSaving}
/>

ResetButton

Reset form button.

import { ResetButton } from '@algodomain/smart-forms'

<ResetButton onClick={handleReset} />

NavigationButtons

Navigation buttons for multi-tab forms.

import { NavigationButtons } from '@algodomain/smart-forms'

<NavigationButtons
  onPrevious={handlePrevious}
  onNext={handleNext}
  onSubmit={handleSubmit}
  onSaveDraft={handleSaveDraft}
  onReset={handleReset}
  isLoading={isLoading}
  isDraftSaving={isDraftSaving}
  config={config}
  isFirstTab={false}
  isLastTab={false}
  disabled={false}
/>

SimpleFormButtons

Simple button group for single-page forms.

import { SimpleFormButtons } from '@algodomain/smart-forms'

<SimpleFormButtons
  onSubmit={handleSubmit}
  onSaveDraft={handleSaveDraft}
  onReset={handleReset}
  isLoading={isLoading}
  isDraftSaving={isDraftSaving}
  config={config}
/>

🎨 Theming & Customization

CSS Variables

Override the default theme by defining CSS variables:

:root {
  --radius: 0.5rem;
  --background: white;
  --foreground: black;
  --primary: #3b82f6;
  --primary-foreground: white;
  --border: #e5e7eb;
  --input: #e5e7eb;
  --ring: #3b82f6;
}

.dark {
  --background: #1f2937;
  --foreground: white;
  --primary: #60a5fa;
}

Tailwind Classes

Customize using Tailwind utility classes:

<SmartForm className="max-w-4xl mx-auto p-8 bg-gray-50 rounded-xl shadow-lg">
  <SmartInput
    field="name"
    label="Name"
    className="border-blue-500 focus:ring-blue-500"
  />
</SmartForm>

Custom Submit Button

Use BaseSmartForm with custom buttons:

import { BaseSmartForm, useSmartForm } from '@algodomain/smart-forms'

function CustomForm() {
  return (
    <BaseSmartForm api="/api/submit">
      <SmartInput field="name" label="Name" />
      <CustomSubmitButton />
    </BaseSmartForm>
  )
}

function CustomSubmitButton() {
  const { submitForm, isLoading } = useSmartForm()
  
  return (
    <button onClick={submitForm} disabled={isLoading}>
      {isLoading ? 'Submitting...' : 'Submit'}
    </button>
  )
}

🪝 Hooks & Context

useSmartForm

Access form context and methods.

import { useSmartForm } from '@algodomain/smart-forms'

function MyComponent() {
  const {
    formData,              // Current form values
    errors,                // Validation errors
    isLoading,             // Submission loading state
    isDraftSaving,         // Draft saving state
    submitForm,            // Submit function
    saveDraft,             // Save draft function
    resetForm,             // Reset to initial values
    updateField,           // Update specific field
    validateField,         // Validate a single field
    validateFields,        // Validate multiple fields
    validateFormAndGetResult, // Validate entire form and return boolean
    registerSubmitHook,    // Register a hook to run before submission
    unregisterSubmitHook,  // Unregister a submit hook
    setErrors,             // Manually set validation errors
    config                 // Form configuration
  } = useSmartForm()
  
  return <button onClick={submitForm}>Submit</button>
}

Methods:

  • validateField(field: string, value: any): boolean - Validate a single field
  • validateFields(fields: string[]): boolean - Validate multiple fields
  • validateFormAndGetResult(): boolean - Validate entire form and return result
  • registerSubmitHook(key: string, hook: () => Promise<void>): void - Register a hook to execute before form submission
  • unregisterSubmitHook(key: string): void - Unregister a submit hook
  • setErrors(errors: Record<string, string>): void - Manually set validation errors

useFormField

Access individual field state (used internally by field components).

import { useFormField } from '@algodomain/smart-forms'

function CustomField({ field }) {
  const { value, error, onChange } = useFormField(field)
  
  return (
    <input
      value={value || ''}
      onChange={(e) => onChange(e.target.value)}
    />
  )
}

useFormWrapper

Alias for useSmartForm (for convenience).

import { useFormWrapper } from '@algodomain/smart-forms'

function MyComponent() {
  const { formData, submitForm } = useFormWrapper()
  // Same as useSmartForm
}

useTabIndex

Get the current tab index in a MultiTabSmartForm.

import { useTabIndex } from '@algodomain/smart-forms'

function MyComponent() {
  const tabIndexContext = useTabIndex()
  const tabIndex = tabIndexContext?.tabIndex ?? 0
  
  return <div>Current tab: {tabIndex + 1}</div>
}

useExternalFormRegistration

Register external forms with MultiTabSmartForm for validation and tab navigation.

import { useExternalFormRegistration } from '@algodomain/smart-forms'

function ExternalForm() {
  const { registerTabFields, currentTabIndex } = useExternalFormRegistration()
  
  useEffect(() => {
    registerTabFields(currentTabIndex, ['field1', 'field2'])
  }, [])
  
  return <div>{/* Your form fields */}</div>
}

useExternalFormFields

Register fields from external forms with a specific tab index.

import { useExternalFormFields } from '@algodomain/smart-forms'

function ExternalForm() {
  useExternalFormFields(['field1', 'field2'], 0) // Register fields for tab 0
  
  return <div>{/* Your form fields */}</div>
}

useAutoDetectFields

Automatically detect and register fields in external forms.

import { useAutoDetectFields, ExternalFieldProvider } from '@algodomain/smart-forms'

function ExternalForm() {
  const { registerField, ExternalFieldProvider } = useAutoDetectFields(0)
  
  return (
    <ExternalFieldProvider registerField={registerField}>
      <SmartInput field="name" label="Name" />
      <SmartInput field="email" label="Email" />
    </ExternalFieldProvider>
  )
}

useExternalTab

Automatically detect both tab index and fields in external forms (recommended).

import { useExternalTab, ExternalFieldProvider } from '@algodomain/smart-forms'

function ExternalForm() {
  const { registerField, ExternalFieldProvider } = useExternalTab()
  
  return (
    <ExternalFieldProvider registerField={registerField}>
      <SmartInput field="name" label="Name" />
      <SmartInput field="email" label="Email" />
    </ExternalFieldProvider>
  )
}

useFieldDetection

Access field detection context (used internally by field components).

import { useFieldDetection } from '@algodomain/smart-forms'

function CustomField({ field }) {
  const context = useFieldDetection()
  
  useEffect(() => {
    if (context) {
      context.registerField(field)
    }
  }, [field, context])
  
  return <input />
}

🔌 Context Providers

TabIndexProvider

Provide tab index context to child components.

import { TabIndexProvider, useTabIndex } from '@algodomain/smart-forms'

function MyComponent() {
  return (
    <TabIndexProvider tabIndex={0}>
      <ChildComponent />
    </TabIndexProvider>
  )
}

function ChildComponent() {
  const context = useTabIndex()
  const tabIndex = context?.tabIndex ?? 0
  return <div>Tab: {tabIndex}</div>
}

ExternalFieldProvider

Provider for automatic field detection in external forms.

import { ExternalFieldProvider } from '@algodomain/smart-forms'

function ExternalForm() {
  const registerField = (fieldName: string) => {
    // Register field with parent form
    console.log('Registering field:', fieldName)
  }
  
  return (
    <ExternalFieldProvider registerField={registerField}>
      <SmartInput field="name" label="Name" />
      <SmartInput field="email" label="Email" />
    </ExternalFieldProvider>
  )
}

💡 Examples

Complete Registration Form

import { SmartForm, FormHeader, Section, Footer } from '@algodomain/smart-forms'
import { SmartInput, SmartSelect, SmartCheckbox, SmartDatePicker } from '@algodomain/smart-forms/fields'
import { FieldEmail, FieldPassword } from '@algodomain/smart-forms/opinionated'
import { z } from 'zod'
import { toast } from 'react-toastify'

export function RegistrationForm() {
  return (
    <SmartForm
      api="https://api.example.com/register"
      method="POST"
      submitButtonText="Register"
      submitDisabled={(formData) => !formData.agreeToTerms}
      onSuccess={(data) => {
        toast.success('Account created successfully!')
      }}
      onError={(error) => {
        toast.error('Registration failed.')
      }}
    >
      <FormHeader 
        title="Create Your Account"
        subTitle="Join us today and get started"
      />
      
      <Section text="Personal Information" />
      
      <SmartInput
        field="fullName"
        label="Full Name"
        validation={z.string().min(3)}
        required
      />
      
      <FieldEmail field="email" required />
      <FieldPassword field="password" required />
      
      <Section text="Location" />
      
      <SmartSelect
        field="country"
        label="Country"
        options={[
          { value: 'us', label: 'United States' },
          { value: 'ca', label: 'Canada' },
          { value: 'uk', label: 'United Kingdom' }
        ]}
        required
      />
      
      <SmartSelect
        field="state"
        label="State"
        options={stateOptions}
        disabled={(formData) => !formData.country}
      />
      
      <SmartDatePicker
        field="birthDate"
        label="Date of Birth"
        validation={z.date().max(new Date())}
        required
      />
      
      <Section text="Agreements" />
      
      <SmartCheckbox
        field="terms"
        label="I agree to the Terms and Conditions"
        validation={z.boolean().refine(val => val === true)}
        required
      />
      
      <Footer>
        <p className="text-sm text-gray-500 text-center">
          By submitting, you agree to our terms and conditions.
        </p>
      </Footer>
    </SmartForm>
  )
}

Multi-Tab Job Application

import { MultiTabSmartForm, Tab, LoadingSpinner } from '@algodomain/smart-forms'
import { SmartInput, SmartTags, SmartDualRangeSlider } from '@algodomain/smart-forms/fields'
import { FieldEmail, FieldPhone } from '@algodomain/smart-forms/opinionated'
import { z } from 'zod'

export function JobApplicationForm() {
  return (
    <MultiTabSmartForm
      api="/api/applications"
      method="POST"
      showProgressBar={true}
      showTabNumbers={true}
      animateTabs={true}
      tabType="underline"
      disableManualTabSwitch={true}
      allowSaveDraft={true}
      enableLocalStorage={true}
      storageKey="job-application"
    >
      <Tab 
        title="Personal Info"
        onNext={async () => {
          // Validate with external API before proceeding
          const response = await fetch('/api/validate-personal-info')
          if (!response.ok) {
            throw new Error('Personal information validation failed')
          }
        }}
      >
        <SmartInput
          field="fullName"
          label="Full Name"
          validation={z.string().min(3)}
          required
        />
        <FieldEmail field="email" required />
        <FieldPhone field="phone" required />
      </Tab>
      
      <Tab 
        title="Experience"
        processingOverlay={
          <div className="flex flex-col items-center gap-2">
            <LoadingSpinner className="h-8 w-8" />
            <p>Processing experience data...</p>
          </div>
        }
      >
        <SmartDualRangeSlider
          minField="minExperience"
          maxField="maxExperience"
          label="Years of Experience"
          min={0}
          max={40}
          step={1}
        />
        <SmartTags
          field="skills"
          label="Skills"
          validation={z.array(z.string()).min(3)}
          required
        />
      </Tab>
    </MultiTabSmartForm>
  )
}

Form with External Components

import { MultiTabSmartForm, Tab, useExternalTab, ExternalFieldProvider } from '@algodomain/smart-forms'
import { SmartInput } from '@algodomain/smart-forms/fields'

// External form component
function ExternalAddressForm() {
  const { registerField, ExternalFieldProvider } = useExternalTab()
  
  return (
    <ExternalFieldProvider registerField={registerField}>
      <SmartInput field="street" label="Street" required />
      <SmartInput field="city" label="City" required />
      <SmartInput field="zipCode" label="Zip Code" required />
    </ExternalFieldProvider>
  )
}

// Main form
export function FormWithExternalComponents() {
  return (
    <MultiTabSmartForm api="/api/submit">
      <Tab title="Basic Info">
        <SmartInput field="name" label="Name" required />
      </Tab>
      
      <Tab title="Address">
        <ExternalAddressForm />
      </Tab>
    </MultiTabSmartForm>
  )
}

Form with Query Parameters

// URL: /create-job?userId=123&companyId=456

<SmartForm
  api="/api/jobs"
  includeQueryParams={true}
  queryParamsToInclude={['userId', 'companyId']}
>
  <SmartInput field="jobTitle" label="Job Title" required />
  <SmartInput field="location" label="Location" required />
</SmartForm>

// Submitted data will include:
// { userId: "123", companyId: "456", jobTitle: "...", location: "..." }

🐛 Troubleshooting

Error: "useSmartForm must be used within a SmartFormProvider"

Cause: Field components used outside a form wrapper.

Solution: Wrap fields in a form component:

// ❌ Wrong
<SmartInput field="name" label="Name" />

// ✅ Correct
<SmartForm api="/api/submit">
  <SmartInput field="name" label="Name" />
</SmartForm>

Styles Not Loading

Cause: CSS not imported.

Solution:

import '@algodomain/smart-forms/style.css'
import 'react-toastify/dist/ReactToastify.css'

For Tailwind v4, add @source directives:

@source "../node_modules/@algodomain/smart-forms/dist/index.js";

Form Not Submitting

Cause: Missing API endpoint or validation errors.

Solution:

  1. Ensure api prop is set
  2. Check console for validation errors
  3. Use logFormData={true}:
<SmartForm api="/api/submit" logFormData={true}>
  {/* Fields */}
</SmartForm>

TypeScript Errors

Solution: Install type definitions:

npm install @types/react @types/react-dom

📖 Validation with Zod

All fields support Zod schemas for validation:

import { z } from 'zod'

// String validation
validation={z.string().min(3, "Too short").max(50, "Too long")}

// Email
validation={z.string().email("Invalid email")}

// Number
validation={z.number().min(0).max(100)}

// Array
validation={z.array(z.string()).min(1, "Required")}

// Date
validation={z.date().max(new Date(), "Cannot be future")}

// Boolean
validation={z.boolean().refine(val => val === true, {
  message: "Must be true"
})}

// Enum
validation={z.enum(['option1', 'option2'])}

// Custom
validation={z.string().refine(val => val.includes('@'), {
  message: "Must contain @"
})}

🤝 Contributing

Contributions welcome! Please visit our GitHub repository.

📄 License

MIT License - feel free to use in your projects!

🙋 Support


Built with ❤️ by AlgoDomain