@algodomain/smart-forms
v0.1.12
Published
A powerful React form framework with smart fields, multi-tab support, and seamless API integration
Maintainers
Readme
@algodomain/smart-forms
A powerful, flexible React form framework with smart fields, multi-tab support, built-in validation, and seamless API integration.
Built with TypeScript, Tailwind CSS, Zod validation, and shadcn/ui components.
📋 Table of Contents
- Features
- Installation
- Quick Start
- Form Components
- Field Components
- Opinionated Fields
- Configuration Reference
- Advanced Features
- UI Components
- Button Components
- Theming
- Hooks & Context
- Context Providers
- Examples
- Troubleshooting
- Contributing
✨ 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-forms2. Install Peer Dependencies
npm install react react-dom tailwindcss zod react-toastify date-fns lucide-react3. 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-2on mobile,gap-4on medium screens and above - Bottom margin:
mb-4for 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
disabledfunction receives the currentformDataobject - It re-evaluates automatically when form data changes
- Returns
trueto disable,falseto enable - Empty strings,
null, andundefinedare 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-interactivehidden: Field is completely removed from the DOM (not rendered)
When to use each:
- Use
disabledwhen you want to show the field exists but is temporarily unavailable - Use
hiddenwhen 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:
SmartInputSmartSelectSmartComboboxSmartCheckboxSmartRadioGroupSmartDatePickerSmartSliderSmartDualRangeSliderSmartTagsSmartFileUploadSmartAutoSuggestTagsSmartBasicRichTextbox
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
queryParamsToIncludeis 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:
- Get access token from localStorage
- Include it as
Authorization: Bearer <token> - Automatically refresh if token expires
- 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 fieldvalidateFields(fields: string[]): boolean- Validate multiple fieldsvalidateFormAndGetResult(): boolean- Validate entire form and return resultregisterSubmitHook(key: string, hook: () => Promise<void>): void- Register a hook to execute before form submissionunregisterSubmitHook(key: string): void- Unregister a submit hooksetErrors(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:
- Ensure
apiprop is set - Check console for validation errors
- 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
- Issues: GitHub Issues
- Email: [email protected]
- npm: @algodomain/smart-forms
Built with ❤️ by AlgoDomain
