name: create-modus-form-component description: Scaffold form components with proper Modus input integration, event handling, validation, and checkbox bug handling
Create Modus Form Component
Scaffold form components with proper Modus input integration, event handling, validation feedback, and checkbox bug handling.
When to Use
Use this skill when:
- Creating forms with Modus input components
- Building multi-step forms
- Implementing form validation
- Handling form submissions
- Creating user registration or contact forms
Pattern Overview
Modus forms follow these patterns:
- Use Modus input components (ModusTextInput, ModusCheckbox, etc.)
- Handle input changes with proper event handlers
- Apply checkbox value inversion automatically
- Use ModusInputFeedback for validation messages
- Include proper accessibility attributes
- Handle form submission with ModusButton
Basic Form Template
"use client";
import { useState } from "react";
import ModusTextInput from "./components/ModusTextInput";
import ModusCheckbox from "./components/ModusCheckbox";
import ModusButton from "./components/ModusButton";
import ModusInputFeedback from "./components/ModusInputFeedback";
import ModusInputLabel from "./components/ModusInputLabel";
interface FormData {
name: string;
email: string;
agreeToTerms: boolean;
}
export default function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: "",
email: "",
agreeToTerms: false,
});
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});
const updateField = (field: keyof FormData, value: string | boolean) => {
setFormData(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
};
const validateForm = (): boolean => {
const newErrors: Partial<Record<keyof FormData, string>> = {};
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.agreeToTerms) {
newErrors.agreeToTerms = "You must agree to the terms";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = () => {
if (validateForm()) {
console.log("Form submitted:", formData);
// Handle form submission
}
};
return (
<div className="max-w-md mx-auto p-6 bg-card border-default rounded-lg">
<div className="text-2xl font-bold mb-6 text-foreground">
Contact Us
</div>
<div className="space-y-4">
{/* Name Field */}
<div>
<ModusInputLabel
label="Name"
required
size="md"
/>
<ModusTextInput
value={formData.name}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateField("name", value);
}}
placeholder="Enter your name"
required
size="md"
/>
{errors.name && (
<ModusInputFeedback
message={errors.name}
type="error"
size="md"
/>
)}
</div>
{/* Email Field */}
<div>
<ModusInputLabel
label="Email"
required
size="md"
/>
<ModusTextInput
type="email"
value={formData.email}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateField("email", value);
}}
placeholder="Enter your email"
required
size="md"
/>
{errors.email && (
<ModusInputFeedback
message={errors.email}
type="error"
size="md"
/>
)}
</div>
{/* Checkbox Field */}
<div>
<ModusCheckbox
label="I agree to the terms and conditions"
value={formData.agreeToTerms}
onValueChange={(event) => {
// ✅ Checkbox value is already corrected (inverted) by wrapper
updateField("agreeToTerms", event.detail);
}}
required
size="md"
/>
{errors.agreeToTerms && (
<ModusInputFeedback
message={errors.agreeToTerms}
type="error"
size="md"
/>
)}
</div>
{/* Submit Button */}
<div className="flex gap-2 pt-4">
<ModusButton
color="primary"
variant="filled"
onButtonClick={handleSubmit}
>
Submit
</ModusButton>
<ModusButton
variant="borderless"
onButtonClick={() => {
setFormData({ name: "", email: "", agreeToTerms: false });
setErrors({});
}}
>
Reset
</ModusButton>
</div>
</div>
</div>
);
}
Form with Multiple Input Types
interface ExtendedFormData {
name: string;
email: string;
phone: string;
message: string;
newsletter: boolean;
notifications: boolean;
}
export default function ExtendedForm() {
const [formData, setFormData] = useState<ExtendedFormData>({
name: "",
email: "",
phone: "",
message: "",
newsletter: false,
notifications: false,
});
const updateTextField = (field: keyof ExtendedFormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const updateCheckboxField = (field: keyof ExtendedFormData, value: boolean) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return (
<div className="space-y-6">
{/* Text Input */}
<div>
<ModusInputLabel label="Name" required />
<ModusTextInput
value={formData.name}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateTextField("name", value);
}}
required
/>
</div>
{/* Email Input */}
<div>
<ModusInputLabel label="Email" required />
<ModusTextInput
type="email"
value={formData.email}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateTextField("email", value);
}}
required
/>
</div>
{/* Phone Input */}
<div>
<ModusInputLabel label="Phone" />
<ModusTextInput
type="tel"
value={formData.phone}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateTextField("phone", value);
}}
/>
</div>
{/* Textarea */}
<div>
<ModusInputLabel label="Message" required />
<ModusTextarea
value={formData.message}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextareaElement).value;
updateTextField("message", value);
}}
rows={4}
required
/>
</div>
{/* Checkboxes */}
<div className="space-y-2">
<ModusCheckbox
label="Subscribe to newsletter"
value={formData.newsletter}
onValueChange={(event) => {
// ✅ Value already corrected by wrapper
updateCheckboxField("newsletter", event.detail);
}}
/>
<ModusCheckbox
label="Enable notifications"
value={formData.notifications}
onValueChange={(event) => {
// ✅ Value already corrected by wrapper
updateCheckboxField("notifications", event.detail);
}}
/>
</div>
{/* Submit */}
<ModusButton
color="primary"
onButtonClick={() => console.log("Form data:", formData)}
>
Submit
</ModusButton>
</div>
);
}
Key Patterns
1. Input Change Handling
// ✅ CORRECT: Extract value from event target
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateField("fieldName", value);
}}
// ❌ WRONG: Don't use event.detail for input values
onInputChange={(event) => {
updateField("fieldName", event.detail); // Wrong!
}}
2. Checkbox Value Handling
// ✅ CORRECT: Use event.detail (already corrected by wrapper)
onValueChange={(event) => {
// event.detail is already inverted/corrected
updateField("checkboxField", event.detail);
}}
// ❌ WRONG: Don't invert again
onValueChange={(event) => {
updateField("checkboxField", !event.detail); // Double inversion!
}}
3. Validation Feedback
{errors.fieldName && (
<ModusInputFeedback
message={errors.fieldName}
type="error"
size="md"
/>
)}
4. Form State Management
// Use useState for form data
const [formData, setFormData] = useState<FormData>({
field1: "",
field2: false,
});
// Update function
const updateField = (field: keyof FormData, value: string | boolean) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
Validation Patterns
Real-time Validation
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState<string | undefined>();
const handleEmailChange = (value: string) => {
setEmail(value);
// Clear error when user types
if (emailError) {
setEmailError(undefined);
}
// Validate on blur or submit
};
const validateEmail = (): boolean => {
if (!email.trim()) {
setEmailError("Email is required");
return false;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setEmailError("Please enter a valid email address");
return false;
}
return true;
};
Form-wide Validation
const validateForm = (): boolean => {
const newErrors: Partial<Record<keyof FormData, string>> = {};
if (!formData.name.trim()) {
newErrors.name = "Name is required";
}
if (!formData.email.trim()) {
newErrors.email = "Email is required";
} else if (!isValidEmail(formData.email)) {
newErrors.email = "Invalid email format";
}
if (!formData.agreeToTerms) {
newErrors.agreeToTerms = "You must agree to the terms";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
Common Form Components
Text Input
<ModusTextInput
value={formData.name}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextInputElement).value;
updateField("name", value);
}}
placeholder="Enter name"
required
label="Name"
/>
Textarea
<ModusTextarea
value={formData.message}
onInputChange={(event) => {
const value = (event.target as HTMLModusWcTextareaElement).value;
updateField("message", value);
}}
rows={4}
placeholder="Enter message"
required
label="Message"
/>
Checkbox
<ModusCheckbox
label="I agree to terms"
value={formData.agreeToTerms}
onValueChange={(event) => {
// ✅ Value already corrected
updateField("agreeToTerms", event.detail);
}}
required
/>
Radio Buttons
<ModusRadio
label="Option 1"
name="choice"
value="option1"
checked={formData.choice === "option1"}
onValueChange={(event) => {
updateField("choice", event.detail);
}}
/>
Accessibility
Always include:
- Labels: Use
ModusInputLabelorlabelprop - Required indicators: Use
requiredprop - Error messages: Use
ModusInputFeedbackwithtype="error" - ARIA attributes: Components handle these automatically
Common Mistakes
- Double checkbox inversion: Don't invert checkbox values - wrapper handles it
- Wrong event value: Use
event.target.valuefor inputs, notevent.detail - Missing validation: Always validate required fields
- No error clearing: Clear errors when user starts typing
- Missing labels: Always provide labels for accessibility
Related Files
src/components/ModusTextInput.tsx- Text input componentsrc/components/ModusCheckbox.tsx- Checkbox component (handles bug)src/components/ModusTextarea.tsx- Textarea componentsrc/components/ModusInputFeedback.tsx- Validation feedbacksrc/components/ModusInputLabel.tsx- Input labelssrc/demos/checkbox-demo/page.tsx- Checkbox examples