Claude-skill-registry form-building
Building create/edit forms with FormField components, validation integration, and save patterns
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-building" ~/.claude/skills/majiayu000-claude-skill-registry-form-building && rm -rf "$T"
manifest:
skills/data/form-building/SKILL.mdsource content
Form Building Skill
Core Principles
- MobX state management - NO state hooks or other libraries unless specified
- Group related fields using
DetailFieldContainer - Ask first if unclear about field importance
- Forms are for CREATE and EDIT operations
Available Components
Located in:
ui/src/common/components/form/fields/
Basic Fields
- Short text fieldsFormFieldText
- Long text fieldsFormFieldTextArea
- Boolean 0/1 fieldsFormFieldCheckbox
- Display-only fieldsFormFieldReadOnly
- Date pickerFormFieldDate
- Color pickerFormFieldColor
- Code editorFormFieldCodeEdit
Single Select (Constants)
import { constants } from "@/models/constants"; <FormFieldSelect record={props.asset} field="status" label="Status" options={constants.asset.status} />
Multi Select (Constants)
<FormFieldMultiSelect record={props.account} field="permissions" label="Permissions" options={constants.account.permissions} />
Model Relationships
Small lists (< 50 options):
<FormFieldModelSelect<AssetModel, OrganizationModel> record={props.asset} field="organization_id" label="Organization" placeholder="Select Organization" modelName="organization" modelSearchField="q" modelDisplayField="label" modelSearchFilters={{ disabled: "0" }} />
Large lists (use search):
<FormFieldModelSearchSelect<AssetModel, OrganizationModel> record={props.asset} field="organization_id" label="Organization" placeholder="Search Organization" modelName="organization" modelSearchField="q" modelDisplayField="label" modelSearchFilters={{ disabled: "0" }} />
Multi-select (small lists):
<FormFieldModelMultiSelect<AssetModel, TagModel> record={props.asset} field="tag_ids" label="Tags" placeholder="Select Tags" modelName="tag" modelSearchField="q" modelDisplayField="label" />
Multi-select (large lists with search):
<FormFieldModelSearchMultiSelect<AssetModel, TagModel> record={props.asset} field="tag_ids" label="Tags" placeholder="Search Tags" modelName="tag" modelSearchField="q" modelDisplayField="label" />
Model Relationship Best Practices
- Always try to use
as modelDisplayFieldlabel - Always try to use
as modelSearchFieldq - If model doesn't have a good label, modify it in
XXModel.ts - Assume all models exist - don't create new ones
JSONB Fields Pattern
For nested JSONB objects:
// JSONB class must extend ValidationClass <FormFieldTextArea record={props.organization.properties} field="pricing_text" label="Pricing Text" placeholder="Enter pricing text..." helpText="Default text if no pricing info in plan" />
Note: Unlike DetailFields, FormFields don't need
parentRecord or SafeBaseModel wrapper
Grouping Fields with Container
<DetailFieldContainer label="Basic Information"> <FormFieldText record={props.asset} field="name" label="Name" placeholder="Asset Name" /> <FormFieldTextArea record={props.asset} field="description" label="Description" placeholder="Description..." /> </DetailFieldContainer> <DetailFieldContainer label="Organization Details"> <FormFieldModelSearchSelect<AssetModel, OrganizationModel> record={props.asset} field="organization_id" label="Organization" modelName="organization" modelSearchField="q" modelDisplayField="label" /> <FormFieldSelect record={props.asset} field="status" label="Status" options={constants.asset.status} /> </DetailFieldContainer>
Complete Form Example
import { observer } from "mobx-react-lite"; import { AssetModel } from "@/models/asset"; import { OrganizationModel } from "@/models/organization"; import { constants } from "@/models/constants"; import { DetailFieldContainer, FormFieldText, FormFieldTextArea, FormFieldSelect, FormFieldModelSearchSelect, FormFieldCheckbox, FormFieldDate, } from "@/ui/common/components/form/fields"; import { Button } from "@/ui/shadcn/ui/button"; interface AssetFormProps { asset: AssetModel; onSave: () => void; onCancel: () => void; } export const AssetForm = observer((props: AssetFormProps) => { const handleSave = async () => { await props.asset.save(); props.onSave(); }; return ( <div className="space-y-6"> <DetailFieldContainer label="Basic Information"> <FormFieldText record={props.asset} field="name" label="Name" placeholder="Asset Name" /> <FormFieldTextArea record={props.asset} field="description" label="Description" placeholder="Asset description..." /> <FormFieldCheckbox record={props.asset} field="is_active" label="Is Active" /> </DetailFieldContainer> <DetailFieldContainer label="Organization & Status"> <FormFieldModelSearchSelect<AssetModel, OrganizationModel> record={props.asset} field="organization_id" label="Organization" modelName="organization" modelSearchField="q" modelDisplayField="label" modelSearchFilters={{ disabled: "0" }} /> <FormFieldSelect record={props.asset} field="status" label="Status" options={constants.asset.status} /> <FormFieldDate record={props.asset} field="effective_date" label="Effective Date" /> </DetailFieldContainer> <div className="flex justify-end gap-2"> <Button variant="outline" onClick={props.onCancel}> Cancel </Button> <Button onClick={handleSave}> Save </Button> </div> </div> ); });
Validation Integration
Forms automatically connect to model validation rules:
// Model already has validation rules defined get validationRules() { return assetValidationRules; } // Form fields automatically show errors <FormFieldText record={props.asset} field="name" label="Name" // Validation errors show automatically />
Save Pattern
const handleSave = async () => { try { await props.asset.save(); // Success - redirect or close modal props.onSave(); } catch (error) { // Error handling - validation errors show on fields automatically console.error("Save failed:", error); } };
Key Differences from Details View
| Feature | Form Fields | Detail Fields |
|---|---|---|
| Use case | Create/Edit | View/Edit |
| JSONB | Direct record | Needs |
| Type casting | Not needed | |
| Save button | Required | Optional |
| Validation | Built-in | Built-in |
Key Rules
- Always create components with
#ui_code_tools - Use search variants for large option lists (>50 items)
- Constants are model-specific:
constants.{model}.{field} - Forms handle validation automatically via model rules
- Call
to persist changesmodel.save() - Ask before implementing if field importance is unclear
- JSONB fields don't need
in formsparentRecord