@tachui/forms - Forms Plugin
The @tachui/forms plugin provides a comprehensive form solution for TachUI with SwiftUI-inspired APIs, advanced validation, state management, and accessibility features.
Overview
- Bundle Size: ~51KB (35KB reduction for non-form apps)
- Components: 10+ form components with variants
- Validation: Built-in rules + custom validation support
- State Management: Reactive form state with field-level control
- Accessibility: WCAG compliant with ARIA support
- TypeScript: Full type safety and IntelliSense support
Installation
npm install @tachui/formsimport { globalTachUIInstance } from '@tachui/core'
import { FormsPlugin } from '@tachui/forms'
// Simple installation using global instance
await globalTachUIInstance.installPlugin(FormsPlugin)
// Or with your own instance
import { SimplifiedTachUIInstance } from '@tachui/core'
const instance = new SimplifiedTachUIInstance()
await instance.installPlugin(FormsPlugin)Plugin Registration
The Forms plugin registers the following components and services:
// Components registered by Forms plugin
- TextField, PasswordField, EmailField, PhoneField, NumberField
- CreditCardField, SearchField, URLField, SSNField, PostalCodeField
- DateField, TimeField, TextArea, ColorField
- Select, MultiSelect, Checkbox, Radio, Switch
- Form, FormSection, SubmitButton
// Services registered by Forms plugin
- formsConfig: Configuration and theme settings
- createFormState: Form state management utilities
- validation: Built-in validation rules and formattersForm Component Usage
The simplified plugin API doesn't change how you use form components: numberFormat?: Intl.NumberFormatOptions
// Accessibility announceErrors?: boolean errorSummaryId?: string }
## Components
### Form
Container component that manages form state and validation:
```typescript
import { Form } from '@tachui/forms'
Form([
TextField({ name: 'email', label: 'Email', type: 'email' }),
TextField({ name: 'password', label: 'Password', type: 'password' }),
Button({ type: 'submit' }, 'Sign In')
], {
onSubmit: async (data, state) => {
console.log('Form data:', data)
console.log('Form state:', state)
await submitLogin(data)
},
validation: {
validateOn: 'blur',
stopOnFirstError: false
}
})Form Props
interface FormProps {
onSubmit?: (data: FormData, state: FormState) => void | Promise<void>
onChange?: (fieldName: string, value: any, fieldState: FieldState) => void
validation?: {
validateOn?: 'change' | 'blur' | 'submit'
stopOnFirstError?: boolean
debounceMs?: number
}
initialValues?: Record<string, any>
resetOnSubmit?: boolean
preserveValues?: boolean
}TextField
Versatile text input component with built-in validation:
import { TextField, EmailField, PasswordField } from '@tachui/forms'
// Basic text field
TextField({
name: 'username',
label: 'Username',
placeholder: 'Enter your username',
required: true
})
// Email field with validation
EmailField({
name: 'email',
label: 'Email Address',
validation: {
rules: ['required', 'email'],
validateOn: 'blur'
}
})
// Password field with strength validation
PasswordField({
name: 'password',
label: 'Password',
showStrengthIndicator: true,
validation: {
rules: ['required', 'minLength'],
validateOn: 'change'
}
})TextField Variants
EmailField- Email input with validationPasswordField- Password input with strength indicatorSearchField- Search input with search stylingURLField- URL input with URL validationPhoneField- Phone input with format validationTextArea- Multiline text input
Checkbox & Switch
Boolean input components with custom styling:
import { Checkbox, Switch, CheckboxGroup } from '@tachui/forms'
// Single checkbox
Checkbox({
name: 'terms',
label: 'I agree to the terms and conditions',
required: true
})
// Switch component
Switch({
name: 'notifications',
label: 'Enable notifications',
size: 'medium'
})
// Checkbox group
CheckboxGroup({
name: 'interests',
label: 'Select your interests',
options: [
{ value: 'tech', label: 'Technology' },
{ value: 'design', label: 'Design' },
{ value: 'business', label: 'Business' }
],
direction: 'vertical'
})Radio & RadioGroup
Single-choice input components:
import { Radio, RadioGroup } from '@tachui/forms'
// Radio group
RadioGroup({
name: 'plan',
label: 'Choose a plan',
options: [
{ value: 'basic', label: 'Basic - $9/month' },
{ value: 'pro', label: 'Pro - $19/month' },
{ value: 'enterprise', label: 'Enterprise - $49/month' }
],
required: true,
direction: 'vertical'
})
// Individual radio
Radio({
name: 'plan',
value: 'basic',
label: 'Basic Plan'
})Select
Dropdown selection component with search and multi-select:
import { Select, MultiSelect, Combobox } from '@tachui/forms'
// Basic select
Select({
name: 'country',
label: 'Country',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' }
],
searchable: true
})
// Multi-select
MultiSelect({
name: 'skills',
label: 'Skills',
options: skillOptions,
multiple: true
})
// Combobox with async options
Combobox({
name: 'city',
label: 'City',
searchable: true,
loadOptions: async (query) => {
const response = await fetch(`/api/cities?q=${query}`)
return response.json()
}
})Validation System
Built-in Rules
// Available validation rules
const rules = [
'required', // Field is required
'email', // Valid email format
'url', // Valid URL format
'number', // Valid number
'integer', // Valid integer
'minLength', // Minimum string length
'maxLength', // Maximum string length
'min', // Minimum numeric value
'max', // Maximum numeric value
'pattern' // Custom regex pattern
]Custom Validation
import { registerValidationRule } from '@tachui/forms'
// Register custom validation rule
registerValidationRule({
name: 'strongPassword',
validate: (value: string) => {
const hasUpper = /[A-Z]/.test(value)
const hasLower = /[a-z]/.test(value)
const hasNumber = /\d/.test(value)
const hasSymbol = /[!@#$%^&*(),.?":{}|<>]/.test(value)
return {
valid: hasUpper && hasLower && hasNumber && hasSymbol,
message: 'Password must contain uppercase, lowercase, number, and symbol'
}
}
})
// Use in component
TextField({
name: 'password',
type: 'password',
validation: {
rules: ['required', 'strongPassword'],
validateOn: 'change'
}
})Cross-Field Validation
import { CrossFieldValidators } from '@tachui/forms'
Form([
TextField({ name: 'password', type: 'password' }),
TextField({ name: 'confirmPassword', type: 'password' })
], {
validation: {
crossField: [
CrossFieldValidators.mustMatch('password', 'confirmPassword', 'Passwords must match')
]
}
})State Management
Form State
Access and control form state:
import { createFormState } from '@tachui/forms'
const formManager = createFormState({
username: '',
email: '',
terms: false
})
// Form state properties
formManager.state.valid // Is form valid?
formManager.state.dirty // Has form been modified?
formManager.state.touched // Has form been interacted with?
formManager.state.submitting // Is form being submitted?
formManager.state.errors // Form-level errors
// Form methods
formManager.setValue('username', 'john')
formManager.validateForm()
formManager.resetForm()
formManager.getFieldState('email')Field State
Control individual field state:
import { createField } from '@tachui/forms'
const field = createField('email', '', {
rules: ['required', 'email'],
validateOn: 'blur'
})
// Field properties
field.value() // Current value
field.error() // Validation error
field.valid() // Is field valid?
field.dirty() // Has field been modified?
field.touched() // Has field been focused?
field.validating() // Is field being validated?
// Field methods
field.setValue('[email protected]')
field.validate()
field.reset()
field.onFocus()
field.onBlur()Multi-Step Forms
Handle complex multi-step forms:
import { createMultiStepFormState } from '@tachui/forms'
const multiStepForm = createMultiStepFormState({
steps: ['personal', 'address', 'payment'],
data: {
personal: { name: '', email: '' },
address: { street: '', city: '' },
payment: { cardNumber: '', expiryDate: '' }
}
})
// Navigation
multiStepForm.nextStep()
multiStepForm.previousStep()
multiStepForm.goToStep('address')
// Validation per step
multiStepForm.validateStep('personal')
multiStepForm.validateAllSteps()
// State
multiStepForm.currentStep() // 'personal'
multiStepForm.canProceed() // Can go to next step?
multiStepForm.progress() // 0.33 (33% complete)Accessibility
ARIA Support
All components include comprehensive ARIA attributes:
TextField({
name: 'email',
label: 'Email Address',
helperText: 'We will never share your email',
error: 'Please enter a valid email',
// Automatically generates:
// aria-label="Email Address"
// aria-describedby="email-helper email-error"
// aria-invalid="true"
// role="textbox"
})Screen Reader Announcements
Form([...fields], {
accessibility: {
announceErrors: true,
announceSuccess: true,
errorSummaryId: 'form-errors'
}
})Keyboard Navigation
- Tab navigation through form fields
- Space/Enter activation for checkboxes/radios
- Arrow key navigation in radio groups
- Escape to close dropdowns
Styling & Theming
CSS Custom Properties
:root {
--tachui-form-gap: 1rem;
--tachui-form-border-color: #d1d5db;
--tachui-form-border-radius: 0.375rem;
--tachui-form-focus-color: #3b82f6;
--tachui-form-error-color: #ef4444;
--tachui-form-success-color: #22c55e;
}Form Styles
import { FormStyles } from '@tachui/forms'
// Predefined form styles
FormStyles.Automatic([...fields]) // Default styling
FormStyles.Grouped([...fields]) // Grouped with container
FormStyles.Inset([...fields]) // Inset styling
FormStyles.Plain([...fields]) // Minimal stylingComponent Styling
TextField({
name: 'email',
className: 'my-custom-field',
style: {
borderColor: 'blue',
borderRadius: '8px'
}
})Form Utilities
Serialization
import { FormUtils } from '@tachui/forms'
const formData = { name: 'John', email: '[email protected]' }
// Convert to different formats
const formDataObj = FormUtils.serialize.toFormData(formData)
const urlParams = FormUtils.serialize.toURLSearchParams(formData)
const jsonString = FormUtils.serialize.toJSON(formData)Validation Presets
import { ValidationPresets } from '@tachui/forms'
// Common validation patterns
ValidationPresets.email
ValidationPresets.phone
ValidationPresets.url
ValidationPresets.strongPassword
ValidationPresets.creditCard
ValidationPresets.postalCodeExamples
Complete Login Form
import { Form, TextField, Checkbox, Button } from '@tachui/forms'
const LoginForm = () => {
return Form([
TextField({
name: 'email',
label: 'Email',
type: 'email',
required: true,
validation: {
rules: ['required', 'email'],
validateOn: 'blur'
}
}),
TextField({
name: 'password',
label: 'Password',
type: 'password',
required: true,
validation: {
rules: ['required', 'minLength'],
validateOn: 'change'
}
}),
Checkbox({
name: 'remember',
label: 'Remember me'
}),
Button({
type: 'submit',
variant: 'primary'
}, 'Sign In')
], {
onSubmit: async (data, state) => {
if (state.valid) {
await login(data.email, data.password, data.remember)
}
},
validation: {
validateOn: 'blur',
showValidationSummary: true
}
})
}Registration Form with Validation
const RegistrationForm = () => {
return Form([
TextField({
name: 'firstName',
label: 'First Name',
required: true
}),
TextField({
name: 'lastName',
label: 'Last Name',
required: true
}),
EmailField({
name: 'email',
label: 'Email Address',
required: true
}),
PasswordField({
name: 'password',
label: 'Password',
showStrengthIndicator: true,
validation: {
rules: ['required', 'strongPassword']
}
}),
TextField({
name: 'confirmPassword',
label: 'Confirm Password',
type: 'password',
required: true
}),
CheckboxGroup({
name: 'interests',
label: 'Interests (optional)',
options: [
{ value: 'tech', label: 'Technology' },
{ value: 'design', label: 'Design' },
{ value: 'business', label: 'Business' }
]
}),
Checkbox({
name: 'terms',
label: 'I agree to the Terms of Service',
required: true
}),
Button({ type: 'submit' }, 'Create Account')
], {
validation: {
validateOn: 'blur',
crossField: [
CrossFieldValidators.mustMatch('password', 'confirmPassword')
]
},
onSubmit: async (data) => {
await createAccount(data)
}
})
}Dynamic Form with Conditional Fields
import { createSignal, createEffect } from '@tachui/core'
const DynamicForm = () => {
const [accountType, setAccountType] = createSignal('personal')
const [showBusiness, setShowBusiness] = createSignal(false)
createEffect(() => {
setShowBusiness(accountType() === 'business')
})
const baseFields = [
RadioGroup({
name: 'accountType',
label: 'Account Type',
options: [
{ value: 'personal', label: 'Personal' },
{ value: 'business', label: 'Business' }
],
onChange: (_, value) => setAccountType(value)
}),
TextField({
name: 'name',
label: 'Full Name',
required: true
})
]
const businessFields = showBusiness() ? [
TextField({
name: 'companyName',
label: 'Company Name',
required: true
}),
TextField({
name: 'taxId',
label: 'Tax ID',
validation: {
rules: ['required', 'pattern'],
pattern: /^\d{2}-\d{7}$/
}
})
] : []
return Form([
...baseFields,
...businessFields,
Button({ type: 'submit' }, 'Submit')
])
}Best Practices
Form Structure
- Use semantic HTML structure with proper labels
- Group related fields with FormSection
- Provide clear error messages and help text
- Include form validation summary for accessibility
Performance
- Use debounced validation for real-time feedback
- Implement field-level validation to avoid full form re-validation
- Lazy load form options for large datasets
- Use controlled components only when necessary
User Experience
- Validate on blur for immediate feedback
- Show validation summary for complex forms
- Provide clear success/error states
- Support keyboard navigation throughout
Accessibility
- Always provide labels for form fields
- Use proper ARIA attributes
- Announce validation errors to screen readers
- Ensure sufficient color contrast for error states
Migration Guide
From React Hook Form
// React Hook Form
const { register, handleSubmit, formState: { errors } } = useForm()
// TachUI Forms
const form = createFormState(initialValues)From Formik
// Formik
<Formik initialValues={...} validationSchema={...} onSubmit={...}>
{({ values, errors, touched, handleChange, handleBlur }) => (
<Form>...</Form>
)}
</Formik>
// TachUI Forms
Form([...fields], {
initialValues: ...,
validation: ...,
onSubmit: ...
})Troubleshooting
Common Issues
Validation not triggering
- Check
validateOnsetting - Ensure validation rules are properly registered
- Check
Form state not updating
- Verify field names are unique
- Check if form context is being passed to fields
TypeScript errors
- Ensure proper import of types from '@tachui/forms'
- Check that validation rules match expected types
Debug Mode
await installPlugin(FormsPlugin, {
dev: {
debug: true // Enables detailed logging
}
})Contributing
The forms plugin is part of the TachUI monorepo. See the main repository for development setup and guidelines.
API Reference
For complete API documentation, see the TypeScript definitions in the @tachui/forms package. All components and utilities are fully typed with comprehensive IntelliSense support.