StratIQX Form Styling Guidelines
Document Information
- Document Title: Comprehensive Form Styling Guidelines for StratIQX Applications
- Version: 1.0
- Date: August 25, 2025
- Purpose: Provide standardized form styling patterns, validation techniques, and implementation guidelines based on proven implementations in the StratIQX onboarding application.
Table of Contents
- Overview & Design Philosophy
- Form Layout Patterns
- Component Structure
- Typography & Color System
- Validation & Error Handling
- State Management
- Accessibility Guidelines
- Implementation Examples
- Best Practices
- Common Patterns & Reusable Components
Overview & Design Philosophy
Core Principles
Our form design follows these fundamental principles derived from successful implementations:
- Consistency: All forms maintain visual and functional consistency across the application
- Clarity: Clear visual hierarchy and intuitive user flow
- Professional Appearance: Clean, modern styling that reflects enterprise software quality
- Accessibility: WCAG 2.1 compliant with proper focus management and screen reader support
- Responsive Design: Forms work seamlessly across all device sizes
- Progressive Enhancement: Core functionality works without JavaScript
Design System Integration
Forms integrate with the existing StratIQX design system using:
- Tailwind CSS with custom CSS variables
- shadcn/ui component patterns
- Lucide React icons for visual enhancement
- Consistent color palette (blues, grays, status colors)
Form Layout Patterns
1. Modal Forms (Complex/Multi-Section)
Use Case: Complex forms with multiple related sections (e.g., CustomIndustryRequestModal)
Structure:
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div className="w-full max-w-2xl bg-white rounded-lg shadow-xl flex flex-col max-h-[90vh]">
{/* Fixed Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 flex-shrink-0">
<div>
<h2 className="text-xl font-semibold text-gray-900">Form Title</h2>
<p className="text-sm text-gray-600 mt-1">Form Description</p>
</div>
<Button variant="ghost" size="sm" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
</div>
{/* Scrollable Content */}
<div className="flex-1 overflow-y-auto">
<div className="p-6 space-y-6">
{/* Info Banner */}
<InfoBanner />
{/* Form Sections */}
<FormSections />
</div>
</div>
{/* Fixed Footer */}
<div className="border-t border-gray-200 p-6 bg-gray-50 flex-shrink-0">
<FormFooter />
</div>
</div>
</div>Key Features:
- Fixed header and footer with scrollable content
- Modal backdrop with proper z-index (z-50)
- Maximum height constraint (max-h-[90vh]) for small screens
- Proper modal accessibility (ESC key, click outside to close)
2. Inline Forms (Embedded/Card-Based)
Use Case: Forms embedded within page content (e.g., Support Contact Form)
Structure:
<Card className="shadow-xl border-0 bg-white max-w-2xl mx-auto">
<CardContent className="p-8">
{/* Form Header */}
<div className="mb-6">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Form Title</h2>
<p className="text-gray-600">Form description</p>
</div>
{/* Info Banner */}
<InfoBanner />
{/* Form Content */}
<div className="space-y-6">
<FormSections />
</div>
{/* Form Footer */}
<FormFooter />
</CardContent>
</Card>Key Features:
- Card-based container with consistent shadows and borders
- Integrated within page flow without overlay
- Responsive width constraints (max-w-2xl mx-auto)
- Consistent padding (p-8) for content areas
Component Structure
1. Info Banners
Purpose: Provide contextual information and set expectations
const InfoBanner: React.FC<{ type?: 'info' | 'warning' | 'success' }> = ({
type = 'info'
}) => (
<div className={`border rounded-lg p-4 mb-6 ${getBannerStyles(type)}`}>
<div className="flex items-start gap-3">
<InfoIcon className="h-5 w-5 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold mb-1">Banner Title</h4>
<p className="text-sm">Descriptive text explaining the form's purpose or important information.</p>
</div>
</div>
</div>
)
const getBannerStyles = (type: string) => {
switch (type) {
case 'info': return 'bg-blue-50 border-blue-200 text-blue-900'
case 'warning': return 'bg-yellow-50 border-yellow-200 text-yellow-900'
case 'success': return 'bg-green-50 border-green-200 text-green-900'
default: return 'bg-blue-50 border-blue-200 text-blue-900'
}
}2. Form Sections
Purpose: Organize related form fields with clear visual hierarchy
const FormSection: React.FC<{ title: string; icon: React.ReactNode; children: React.ReactNode }> = ({
title,
icon,
children
}) => (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
<div className="text-blue-600">{icon}</div>
{title}
</h3>
<div className="space-y-4">
{children}
</div>
</div>
)3. Form Fields
Purpose: Consistent field styling with proper validation states
Input Fields:
<div className="space-y-2">
<Label htmlFor="fieldId">Field Label {required && '*'}</Label>
<Input
id="fieldId"
type="text"
placeholder="Placeholder text"
value={value}
onChange={(e) => handleInputChange('fieldName', e.target.value)}
className={errors.fieldName ? 'border-red-300' : ''}
/>
{errors.fieldName && (
<p className="text-sm text-red-600">{errors.fieldName}</p>
)}
</div>Select Fields:
<div className="space-y-2">
<Label htmlFor="selectId">Select Label *</Label>
<select
id="selectId"
value={value}
onChange={(e) => handleInputChange('fieldName', e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value="">Select an option...</option>
{options.map(option => (
<option key={option.id} value={option.value}>
{option.label}
</option>
))}
</select>
{errors.fieldName && (
<p className="text-sm text-red-600">{errors.fieldName}</p>
)}
</div>Textarea Fields:
<div className="space-y-2">
<Label htmlFor="textareaId">Message *</Label>
<Textarea
id="textareaId"
placeholder="Detailed description..."
value={value}
onChange={(e) => handleInputChange('fieldName', e.target.value)}
className={cn("min-h-[120px] resize-none", errors.fieldName ? 'border-red-300' : '')}
/>
{errors.fieldName && (
<p className="text-sm text-red-600">{errors.fieldName}</p>
)}
<p className="text-xs text-gray-500">
Character count: {value.length} {minLength && `(minimum ${minLength})`}
</p>
</div>4. Form Footer
Purpose: Consistent action area with proper button hierarchy
const FormFooter: React.FC<{
isSubmitting: boolean
onSubmit: () => void
onCancel?: () => void
}> = ({ isSubmitting, onSubmit, onCancel }) => (
<div className="border-t border-gray-200 pt-6 mt-6 bg-gray-50 -mx-8 px-8 pb-8 rounded-b-lg">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-600">* Required fields</p>
<div className="flex gap-3">
{onCancel && (
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
Cancel
</Button>
)}
<Button onClick={onSubmit} disabled={isSubmitting} className="flex items-center gap-2">
{isSubmitting ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Submitting...
</>
) : (
<>
<Send className="w-4 h-4" />
Submit
</>
)}
</Button>
</div>
</div>
<p className="text-xs text-gray-500 text-center mt-4">
Privacy notice or additional information
</p>
</div>
)Typography & Color System
Typography Hierarchy
/* Form Titles */
.form-title {
@apply text-xl font-semibold text-gray-900;
}
/* Section Headers */
.section-header {
@apply text-lg font-semibold text-gray-900;
}
/* Field Labels */
.field-label {
@apply text-sm font-medium text-gray-700;
}
/* Help Text */
.help-text {
@apply text-xs text-gray-500;
}
/* Error Text */
.error-text {
@apply text-sm text-red-600;
}Color Palette
const formColors = {
// Text Colors
primary: 'text-gray-900', // Main headings
secondary: 'text-gray-600', // Descriptions
muted: 'text-gray-500', // Help text
error: 'text-red-600', // Error messages
success: 'text-green-600', // Success states
// Background Colors
cardBg: 'bg-white', // Form containers
footerBg: 'bg-gray-50', // Footer sections
infoBg: 'bg-blue-50', // Info banners
errorBg: 'bg-red-50', // Error states
// Border Colors
border: 'border-gray-300', // Default borders
focusBorder: 'border-blue-500', // Focus states
errorBorder: 'border-red-300', // Error states
// Icon Colors
iconPrimary: 'text-blue-600', // Section icons
iconMuted: 'text-gray-400', // Supporting icons
}Validation & Error Handling
1. Validation Function Pattern
const validateForm = (): boolean => {
const newErrors: Partial<FormData> = {}
// Required field validation
if (!formData.email.trim()) {
newErrors.email = 'Email is required'
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Please enter a valid email address'
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required'
} else if (formData.message.trim().length < 10) {
newErrors.message = 'Message must be at least 10 characters long'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}2. Real-Time Validation
const handleInputChange = (field: keyof FormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }))
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }))
}
}3. Error Display Pattern
// Field-level errors
{errors.fieldName && (
<p className="text-sm text-red-600 flex items-center gap-1">
<AlertCircle className="h-4 w-4" />
{errors.fieldName}
</p>
)}
// Form-level error summary
{Object.keys(errors).length > 0 && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div className="flex items-start gap-3">
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold text-red-900 mb-1">Please fix the following errors:</h4>
<ul className="text-sm text-red-800 list-disc list-inside space-y-1">
{Object.values(errors).map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</div>
</div>
</div>
)}4. Success States
const SuccessState: React.FC<{ onReset: () => void }> = ({ onReset }) => (
<div className="text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
Success Message
</h3>
<p className="text-gray-600 mb-4">
Detailed success description
</p>
<p className="text-sm text-gray-500 mb-6">
Additional instructions or next steps
</p>
<Button variant="outline" onClick={onReset}>
Continue
</Button>
</div>
)State Management
1. Form State Hook Pattern
interface FormState<T> {
data: T
errors: Partial<T>
isSubmitting: boolean
isSuccess: boolean
}
const useFormState = <T extends Record<string, any>>(initialData: T) => {
const [formState, setFormState] = useState<FormState<T>>({
data: initialData,
errors: {},
isSubmitting: false,
isSuccess: false
})
const setData = (field: keyof T, value: any) => {
setFormState(prev => ({
...prev,
data: { ...prev.data, [field]: value },
errors: { ...prev.errors, [field]: undefined }
}))
}
const setErrors = (errors: Partial<T>) => {
setFormState(prev => ({ ...prev, errors }))
}
const setSubmitting = (isSubmitting: boolean) => {
setFormState(prev => ({ ...prev, isSubmitting }))
}
const setSuccess = (isSuccess: boolean) => {
setFormState(prev => ({ ...prev, isSuccess }))
}
const reset = () => {
setFormState({
data: initialData,
errors: {},
isSubmitting: false,
isSuccess: false
})
}
return {
...formState,
setData,
setErrors,
setSubmitting,
setSuccess,
reset
}
}2. Form Submission Pattern
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) return
setSubmitting(true)
try {
await onSubmit(formData)
setSuccess(true)
// Auto-close modal after success (for modals)
if (autoClose) {
setTimeout(() => {
onClose()
reset()
}, 2000)
}
} catch (error) {
console.error('Form submission error:', error)
// Handle submission errors
} finally {
setSubmitting(false)
}
}Accessibility Guidelines
1. Keyboard Navigation
- All interactive elements must be keyboard accessible
- Proper tab order through form fields
- ESC key closes modals
- Enter key submits forms (when appropriate)
2. Screen Reader Support
// Proper labeling
<Label htmlFor="email">Email Address *</Label>
<Input
id="email"
aria-describedby="email-error email-help"
aria-invalid={!!errors.email}
/>
{errors.email && (
<p id="email-error" role="alert" className="text-sm text-red-600">
{errors.email}
</p>
)}
<p id="email-help" className="text-xs text-gray-500">
We'll use this to contact you about your request
</p>3. Focus Management
// Modal focus management
useEffect(() => {
if (isOpen && modalRef.current) {
const focusableElement = modalRef.current.querySelector('[tabindex="0"]')
if (focusableElement) {
(focusableElement as HTMLElement).focus()
}
}
}, [isOpen])
// Trap focus within modal
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
trapFocus(e, modalRef.current)
}
if (e.key === 'Escape') {
onClose()
}
}4. Color Contrast
- All text meets WCAG AA standards (4.5:1 contrast ratio)
- Error states use sufficient color contrast
- Focus indicators are clearly visible
Implementation Examples
Complete Modal Form Example
import React, { useState } from 'react'
import { Button } from '../ui/Button'
import { Input } from '../ui/Input'
import { Textarea } from '../ui/Textarea'
import { Label } from '../ui/Label'
import { X, Send, CheckCircle, Building2, Users } from 'lucide-react'
import { cn } from '../../lib/utils'
interface ModalFormData {
name: string
email: string
message: string
}
interface ModalFormProps {
isOpen: boolean
onClose: () => void
onSubmit?: (data: ModalFormData) => Promise<void>
}
export const ExampleModalForm: React.FC<ModalFormProps> = ({
isOpen,
onClose,
onSubmit
}) => {
const [formData, setFormData] = useState<ModalFormData>({
name: '',
email: '',
message: ''
})
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [errors, setErrors] = useState<Partial<ModalFormData>>({})
const validateForm = (): boolean => {
const newErrors: Partial<ModalFormData> = {}
if (!formData.name.trim()) {
newErrors.name = 'Name is required'
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required'
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Please enter a valid email address'
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleInputChange = (field: keyof ModalFormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }))
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }))
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) return
setIsSubmitting(true)
try {
if (onSubmit) {
await onSubmit(formData)
}
setIsSuccess(true)
setTimeout(() => {
onClose()
resetForm()
}, 2000)
} catch (error) {
console.error('Form submission error:', error)
} finally {
setIsSubmitting(false)
}
}
const resetForm = () => {
setFormData({ name: '', email: '', message: '' })
setErrors({})
setIsSuccess(false)
setIsSubmitting(false)
}
if (!isOpen) return null
if (isSuccess) {
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div className="w-full max-w-md bg-white rounded-lg shadow-xl">
<div className="p-8 text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
Form Submitted Successfully!
</h3>
<p className="text-gray-600 mb-4">
Thank you for your submission. We'll get back to you soon.
</p>
</div>
</div>
</div>
)
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<div className="w-full max-w-2xl bg-white rounded-lg shadow-xl flex flex-col max-h-[90vh]">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 flex-shrink-0">
<div>
<h2 className="text-xl font-semibold text-gray-900">
Example Modal Form
</h2>
<p className="text-sm text-gray-600 mt-1">
Fill out this form to get in touch
</p>
</div>
<Button variant="ghost" size="sm" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto">
<div className="p-6 space-y-6">
{/* Info Banner */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-start gap-3">
<Building2 className="h-5 w-5 text-blue-600 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold text-blue-900 mb-1">
Quick Contact Form
</h4>
<p className="text-sm text-blue-800">
We'll respond to your message within 24 hours during business days.
</p>
</div>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Contact Information */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
<Users className="h-5 w-5 text-blue-600" />
Contact Information
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name">Name *</Label>
<Input
id="name"
placeholder="Your full name"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className={errors.name ? 'border-red-300' : ''}
/>
{errors.name && (
<p className="text-sm text-red-600">{errors.name}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">Email Address *</Label>
<Input
id="email"
type="email"
placeholder="your.email@company.com"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className={errors.email ? 'border-red-300' : ''}
/>
{errors.email && (
<p className="text-sm text-red-600">{errors.email}</p>
)}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="message">Message *</Label>
<Textarea
id="message"
placeholder="Please describe what you need help with..."
value={formData.message}
onChange={(e) => handleInputChange('message', e.target.value)}
className={cn("min-h-[120px] resize-none", errors.message ? 'border-red-300' : '')}
/>
{errors.message && (
<p className="text-sm text-red-600">{errors.message}</p>
)}
</div>
</div>
</form>
</div>
</div>
{/* Footer */}
<div className="border-t border-gray-200 p-6 bg-gray-50 flex-shrink-0">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-600">* Required fields</p>
<div className="flex gap-3">
<Button type="button" variant="outline" onClick={onClose} disabled={isSubmitting}>
Cancel
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
Submitting...
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
Submit
</>
)}
</Button>
</div>
</div>
</div>
</div>
</div>
)
}Best Practices
1. Performance Optimization
- Debounce validation for real-time feedback
- Memoize validation functions to prevent unnecessary re-renders
- Lazy load complex form components when needed
2. User Experience
- Progressive disclosure - show fields as needed
- Clear error messages with actionable instructions
- Loading states for all async operations
- Success feedback with clear next steps
3. Code Organization
- Separate concerns - validation, submission, UI components
- Reusable components for common patterns
- Type safety with TypeScript interfaces
- Consistent naming conventions
4. Testing Considerations
- Unit tests for validation functions
- Integration tests for form submission flows
- Accessibility tests for screen reader compatibility
- Visual regression tests for consistent styling
Common Patterns & Reusable Components
1. FormField Wrapper Component
interface FormFieldProps {
label: string
error?: string
required?: boolean
helpText?: string
children: React.ReactNode
}
const FormField: React.FC<FormFieldProps> = ({
label,
error,
required,
helpText,
children
}) => (
<div className="space-y-2">
<Label className="text-sm font-medium text-gray-700">
{label} {required && <span className="text-red-500">*</span>}
</Label>
{children}
{error && (
<p className="text-sm text-red-600 flex items-center gap-1">
<AlertCircle className="h-4 w-4" />
{error}
</p>
)}
{helpText && !error && (
<p className="text-xs text-gray-500">{helpText}</p>
)}
</div>
)2. LoadingButton Component
interface LoadingButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean
loadingText?: string
icon?: React.ReactNode
}
const LoadingButton: React.FC<LoadingButtonProps> = ({
loading = false,
loadingText = 'Loading...',
icon,
children,
disabled,
...props
}) => (
<Button disabled={disabled || loading} {...props}>
{loading ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
{loadingText}
</>
) : (
<>
{icon && <span className="mr-2">{icon}</span>}
{children}
</>
)}
</Button>
)3. FormSection Component
interface FormSectionProps {
title: string
icon?: React.ReactNode
description?: string
children: React.ReactNode
}
const FormSection: React.FC<FormSectionProps> = ({
title,
icon,
description,
children
}) => (
<div className="space-y-4">
<div className="border-b border-gray-200 pb-2">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
{icon && <span className="text-blue-600">{icon}</span>}
{title}
</h3>
{description && (
<p className="text-sm text-gray-600 mt-1">{description}</p>
)}
</div>
<div className="space-y-4">
{children}
</div>
</div>
)Conclusion
This guide provides a comprehensive foundation for creating consistent, professional, and accessible forms across the StratIQX application. By following these patterns and guidelines, developers can ensure a unified user experience while maintaining code quality and accessibility standards.
Key Takeaways:
- Consistency is crucial - Use the same patterns across all forms
- User experience matters - Clear feedback, proper validation, and accessible design
- Code quality - Type safety, reusable components, and proper error handling
- Performance - Optimize for large forms and complex validation
- Accessibility - Design for all users from the beginning
For questions or suggestions regarding these guidelines, please refer to the implementation examples in the codebase or reach out to the development team.
Document Version: 1.0
Last Updated: August 25, 2025
Next Review: December 2025