Claude-skill-registry form-components
StepLeague reusable form components with accessibility features. Use when creating any form, input field, select dropdown, checkbox, or file upload in the application. Keywords: form, input, select, checkbox, textarea, file upload, accessibility, FormInput, FormSelect.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/form-components" ~/.claude/skills/majiayu000-claude-skill-registry-form-components && rm -rf "$T"
manifest:
skills/data/form-components/SKILL.mdsource content
Form Components Skill
Core Rule
ALWAYS use form components from
.@/components/ui/form-fields
These components:
- Auto-generate
/id
attributesname - Include accessibility features (aria-describedby, aria-invalid)
- Consistent styling across the app
- Proper label association
Available Components
| Component | Purpose |
|---|---|
| Text, email, password, number inputs |
| Dropdown select |
| Checkbox with label |
| Multi-line text input |
| File upload |
Usage Examples
FormInput
import { FormInput } from "@/components/ui/form-fields"; <FormInput fieldName="user-email" label="Email Address" type="email" placeholder="you@example.com" required error={errors.email} description="We'll never share your email" /> <FormInput fieldName="step-count" label="Steps" type="number" min={0} max={100000} value={steps} onChange={(e) => setSteps(e.target.value)} />
FormSelect
import { FormSelect } from "@/components/ui/form-fields"; <FormSelect fieldName="league-select" label="Select League" value={selectedLeague} onChange={(e) => setSelectedLeague(e.target.value)} required > <option value="">Choose a league...</option> {leagues.map((league) => ( <option key={league.id} value={league.id}> {league.name} </option> ))} </FormSelect>
FormCheckbox
import { FormCheckbox } from "@/components/ui/form-fields"; <FormCheckbox fieldName="accept-terms" label="I accept the terms and conditions" checked={acceptedTerms} onChange={(e) => setAcceptedTerms(e.target.checked)} required /> <FormCheckbox fieldName="remember-me" label="Remember me on this device" description="We'll keep you logged in for 30 days" />
FormTextarea
import { FormTextarea } from "@/components/ui/form-fields"; <FormTextarea fieldName="feedback" label="Your Feedback" placeholder="Tell us what you think..." rows={4} maxLength={500} value={feedback} onChange={(e) => setFeedback(e.target.value)} />
FormFileInput
import { FormFileInput } from "@/components/ui/form-fields"; <FormFileInput fieldName="screenshot" label="Upload Screenshot" accept="image/*" onChange={handleFileChange} description="PNG, JPG, or GIF up to 5MB" error={fileError} />
Props Reference
Common Props (All Components)
| Prop | Type | Required | Description |
|---|---|---|---|
| | ✅ | Unique identifier (becomes id/name) |
| | ✅ | Label text |
| | ❌ | Error message to display |
| | ❌ | Help text below field |
| | ❌ | Adds required indicator |
| | ❌ | Disables the field |
| | ❌ | Additional CSS classes |
FormInput Additional Props
| Prop | Type | Description |
|---|---|---|
| | Input type (text, email, password, number, etc.) |
| | Placeholder text |
, | | For number inputs |
| | Validation pattern |
FormSelect Additional Props
| Prop | Type | Description |
|---|---|---|
| | Option elements |
| | First disabled option text |
Accessibility Features
The form components automatically provide:
1. Label Association
<!-- Generated HTML --> <label for="user-email">Email Address</label> <input id="user-email" name="user-email" />
2. Error Announcements
<!-- When error prop is provided --> <input id="user-email" aria-invalid="true" aria-describedby="user-email-error" /> <p id="user-email-error" role="alert">Invalid email address</p>
3. Description Association
<input aria-describedby="user-email-description" /> <p id="user-email-description">We'll never share your email</p>
Form Patterns
Basic Form
import { FormInput, FormSelect, FormCheckbox } from "@/components/ui/form-fields"; function MyForm() { const [formData, setFormData] = useState({ email: '', role: '', newsletter: false, }); const [errors, setErrors] = useState({}); return ( <form onSubmit={handleSubmit}> <FormInput fieldName="email" label="Email" type="email" value={formData.email} onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))} error={errors.email} required /> <FormSelect fieldName="role" label="Role" value={formData.role} onChange={(e) => setFormData(prev => ({ ...prev, role: e.target.value }))} required > <option value="">Select role...</option> <option value="member">Member</option> <option value="admin">Admin</option> </FormSelect> <FormCheckbox fieldName="newsletter" label="Subscribe to newsletter" checked={formData.newsletter} onChange={(e) => setFormData(prev => ({ ...prev, newsletter: e.target.checked }))} /> <button type="submit">Submit</button> </form> ); }
With Loading State
<FormInput fieldName="email" label="Email" disabled={isSubmitting} /> <button type="submit" disabled={isSubmitting}> {isSubmitting ? "Saving..." : "Save"} </button>
Anti-Patterns
❌ Don't Use Raw HTML Elements
// ❌ WRONG <label>Email</label> <input type="email" name="email" /> // ✅ CORRECT <FormInput fieldName="email" label="Email" type="email" />
❌ Don't Forget fieldName
// ❌ WRONG - no fieldName <FormInput label="Email" type="email" /> // ✅ CORRECT <FormInput fieldName="email" label="Email" type="email" />
❌ Don't Duplicate IDs
// ❌ WRONG - duplicate fieldNames <FormInput fieldName="email" label="Email" /> <FormInput fieldName="email" label="Confirm Email" /> // ✅ CORRECT - unique fieldNames <FormInput fieldName="email" label="Email" /> <FormInput fieldName="confirm-email" label="Confirm Email" />
Reference Files
| File | Purpose |
|---|---|
| Component implementations |
| Full documentation |
Related Skills
- Form styling and themingdesign-system
- Use existing componentsarchitecture-philosophy